iam-policy-validator 1.14.6__py3-none-any.whl → 1.15.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.
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/METADATA +34 -23
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/RECORD +42 -29
- iam_policy_validator-1.15.0.dist-info/entry_points.txt +4 -0
- iam_validator/__version__.py +1 -1
- iam_validator/checks/__init__.py +2 -0
- iam_validator/checks/action_validation.py +91 -27
- iam_validator/checks/not_action_not_resource.py +163 -0
- iam_validator/checks/resource_validation.py +132 -81
- iam_validator/checks/wildcard_resource.py +136 -6
- iam_validator/commands/__init__.py +3 -0
- iam_validator/commands/cache.py +66 -24
- iam_validator/commands/completion.py +94 -15
- iam_validator/commands/mcp.py +210 -0
- iam_validator/commands/query.py +489 -65
- iam_validator/core/aws_service/__init__.py +5 -1
- iam_validator/core/aws_service/cache.py +20 -0
- iam_validator/core/aws_service/fetcher.py +180 -11
- iam_validator/core/aws_service/storage.py +14 -6
- iam_validator/core/aws_service/validators.py +32 -41
- iam_validator/core/check_registry.py +100 -35
- iam_validator/core/config/aws_global_conditions.py +13 -0
- iam_validator/core/config/check_documentation.py +104 -51
- iam_validator/core/config/config_loader.py +39 -3
- iam_validator/core/config/defaults.py +6 -0
- iam_validator/core/constants.py +11 -4
- iam_validator/core/models.py +39 -14
- iam_validator/mcp/__init__.py +162 -0
- iam_validator/mcp/models.py +118 -0
- iam_validator/mcp/server.py +2928 -0
- iam_validator/mcp/session_config.py +319 -0
- iam_validator/mcp/templates/__init__.py +79 -0
- iam_validator/mcp/templates/builtin.py +856 -0
- iam_validator/mcp/tools/__init__.py +72 -0
- iam_validator/mcp/tools/generation.py +888 -0
- iam_validator/mcp/tools/org_config_tools.py +263 -0
- iam_validator/mcp/tools/query.py +395 -0
- iam_validator/mcp/tools/validation.py +376 -0
- iam_validator/sdk/__init__.py +64 -63
- iam_validator/sdk/context.py +3 -2
- iam_validator/sdk/policy_utils.py +31 -5
- iam_policy_validator-1.14.6.dist-info/entry_points.txt +0 -2
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.14.6.dist-info → iam_policy_validator-1.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
"""Built-in IAM policy templates for MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides pre-defined policy templates with variable substitution
|
|
4
|
+
for common AWS use cases. Each template includes security best practices
|
|
5
|
+
such as condition keys for transport security, resource boundaries, and
|
|
6
|
+
least-privilege principles.
|
|
7
|
+
|
|
8
|
+
Templates are designed to be used with the security enforcement layer
|
|
9
|
+
which validates and enhances them before returning to users.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from string import Template
|
|
13
|
+
from typing import Any, Final
|
|
14
|
+
|
|
15
|
+
# ============================================================================
|
|
16
|
+
# Template Definitions
|
|
17
|
+
# ============================================================================
|
|
18
|
+
|
|
19
|
+
# Each template has:
|
|
20
|
+
# - name: Unique identifier
|
|
21
|
+
# - description: What the template does
|
|
22
|
+
# - variables: List of variable definitions with name, description, required, default
|
|
23
|
+
# - policy: The IAM policy template with ${variable} placeholders
|
|
24
|
+
|
|
25
|
+
TEMPLATES: Final[dict[str, dict[str, Any]]] = {
|
|
26
|
+
"s3-read-only": {
|
|
27
|
+
"name": "s3-read-only",
|
|
28
|
+
"description": "Read-only access to an S3 bucket with optional prefix filtering",
|
|
29
|
+
"variables": [
|
|
30
|
+
{
|
|
31
|
+
"name": "bucket_name",
|
|
32
|
+
"description": "Name of the S3 bucket (without arn:aws:s3::: prefix)",
|
|
33
|
+
"required": True,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "prefix",
|
|
37
|
+
"description": "Optional prefix to restrict access to specific paths (e.g., 'data/' or leave empty for full bucket)",
|
|
38
|
+
"required": False,
|
|
39
|
+
"default": "",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
"policy": {
|
|
43
|
+
"Version": "2012-10-17",
|
|
44
|
+
"Statement": [
|
|
45
|
+
{
|
|
46
|
+
"Sid": "S3ListBucket",
|
|
47
|
+
"Effect": "Allow",
|
|
48
|
+
"Action": [
|
|
49
|
+
"s3:ListBucket",
|
|
50
|
+
"s3:GetBucketLocation",
|
|
51
|
+
"s3:GetBucketVersioning",
|
|
52
|
+
],
|
|
53
|
+
"Resource": "arn:aws:s3:::${bucket_name}",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"Sid": "S3GetObjects",
|
|
57
|
+
"Effect": "Allow",
|
|
58
|
+
"Action": [
|
|
59
|
+
"s3:GetObject",
|
|
60
|
+
"s3:GetObjectVersion",
|
|
61
|
+
"s3:GetObjectMetadata",
|
|
62
|
+
],
|
|
63
|
+
"Resource": "arn:aws:s3:::${bucket_name}/${prefix}*",
|
|
64
|
+
"Condition": {
|
|
65
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
"s3-read-write": {
|
|
72
|
+
"name": "s3-read-write",
|
|
73
|
+
"description": "Read and write access to an S3 bucket with optional prefix filtering",
|
|
74
|
+
"variables": [
|
|
75
|
+
{
|
|
76
|
+
"name": "bucket_name",
|
|
77
|
+
"description": "Name of the S3 bucket (without arn:aws:s3::: prefix)",
|
|
78
|
+
"required": True,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "prefix",
|
|
82
|
+
"description": "Optional prefix to restrict access to specific paths (e.g., 'data/' or leave empty for full bucket)",
|
|
83
|
+
"required": False,
|
|
84
|
+
"default": "",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
"policy": {
|
|
88
|
+
"Version": "2012-10-17",
|
|
89
|
+
"Statement": [
|
|
90
|
+
{
|
|
91
|
+
"Sid": "S3ListBucket",
|
|
92
|
+
"Effect": "Allow",
|
|
93
|
+
"Action": [
|
|
94
|
+
"s3:ListBucket",
|
|
95
|
+
"s3:GetBucketLocation",
|
|
96
|
+
"s3:GetBucketVersioning",
|
|
97
|
+
],
|
|
98
|
+
"Resource": "arn:aws:s3:::${bucket_name}",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"Sid": "S3ReadWriteObjects",
|
|
102
|
+
"Effect": "Allow",
|
|
103
|
+
"Action": [
|
|
104
|
+
"s3:GetObject",
|
|
105
|
+
"s3:GetObjectVersion",
|
|
106
|
+
"s3:PutObject",
|
|
107
|
+
"s3:PutObjectAcl",
|
|
108
|
+
"s3:DeleteObject",
|
|
109
|
+
"s3:DeleteObjectVersion",
|
|
110
|
+
],
|
|
111
|
+
"Resource": "arn:aws:s3:::${bucket_name}/${prefix}*",
|
|
112
|
+
"Condition": {
|
|
113
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
"lambda-basic-execution": {
|
|
120
|
+
"name": "lambda-basic-execution",
|
|
121
|
+
"description": "Basic Lambda execution permissions including CloudWatch Logs",
|
|
122
|
+
"variables": [
|
|
123
|
+
{
|
|
124
|
+
"name": "account_id",
|
|
125
|
+
"description": "AWS account ID (12-digit number)",
|
|
126
|
+
"required": True,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "region",
|
|
130
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
131
|
+
"required": True,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "function_name",
|
|
135
|
+
"description": "Lambda function name or prefix pattern (e.g., my-function or dev-*)",
|
|
136
|
+
"required": True,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
"policy": {
|
|
140
|
+
"Version": "2012-10-17",
|
|
141
|
+
"Statement": [
|
|
142
|
+
{
|
|
143
|
+
"Sid": "LambdaBasicExecution",
|
|
144
|
+
"Effect": "Allow",
|
|
145
|
+
"Action": [
|
|
146
|
+
"lambda:GetFunction",
|
|
147
|
+
"lambda:GetFunctionConfiguration",
|
|
148
|
+
"lambda:InvokeFunction",
|
|
149
|
+
],
|
|
150
|
+
"Resource": "arn:aws:lambda:${region}:${account_id}:function:${function_name}",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"Sid": "CloudWatchLogsAccess",
|
|
154
|
+
"Effect": "Allow",
|
|
155
|
+
"Action": [
|
|
156
|
+
"logs:CreateLogGroup",
|
|
157
|
+
"logs:CreateLogStream",
|
|
158
|
+
"logs:PutLogEvents",
|
|
159
|
+
],
|
|
160
|
+
"Resource": "arn:aws:logs:${region}:${account_id}:log-group:/aws/lambda/${function_name}:*",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
"lambda-s3-trigger": {
|
|
166
|
+
"name": "lambda-s3-trigger",
|
|
167
|
+
"description": "Lambda function with S3 read access for event triggers",
|
|
168
|
+
"variables": [
|
|
169
|
+
{
|
|
170
|
+
"name": "bucket_name",
|
|
171
|
+
"description": "Name of the S3 bucket that triggers the Lambda",
|
|
172
|
+
"required": True,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "function_name",
|
|
176
|
+
"description": "Lambda function name",
|
|
177
|
+
"required": True,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "account_id",
|
|
181
|
+
"description": "AWS account ID (12-digit number)",
|
|
182
|
+
"required": True,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "region",
|
|
186
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
187
|
+
"required": True,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
"policy": {
|
|
191
|
+
"Version": "2012-10-17",
|
|
192
|
+
"Statement": [
|
|
193
|
+
{
|
|
194
|
+
"Sid": "S3ReadAccess",
|
|
195
|
+
"Effect": "Allow",
|
|
196
|
+
"Action": [
|
|
197
|
+
"s3:GetObject",
|
|
198
|
+
"s3:GetObjectVersion",
|
|
199
|
+
],
|
|
200
|
+
"Resource": "arn:aws:s3:::${bucket_name}/*",
|
|
201
|
+
"Condition": {
|
|
202
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"Sid": "S3ListBucket",
|
|
207
|
+
"Effect": "Allow",
|
|
208
|
+
"Action": "s3:ListBucket",
|
|
209
|
+
"Resource": "arn:aws:s3:::${bucket_name}",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"Sid": "LambdaLogging",
|
|
213
|
+
"Effect": "Allow",
|
|
214
|
+
"Action": [
|
|
215
|
+
"logs:CreateLogGroup",
|
|
216
|
+
"logs:CreateLogStream",
|
|
217
|
+
"logs:PutLogEvents",
|
|
218
|
+
],
|
|
219
|
+
"Resource": "arn:aws:logs:${region}:${account_id}:log-group:/aws/lambda/${function_name}:*",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
"dynamodb-crud": {
|
|
225
|
+
"name": "dynamodb-crud",
|
|
226
|
+
"description": "Full CRUD access to a DynamoDB table including indexes",
|
|
227
|
+
"variables": [
|
|
228
|
+
{
|
|
229
|
+
"name": "table_name",
|
|
230
|
+
"description": "DynamoDB table name",
|
|
231
|
+
"required": True,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"name": "region",
|
|
235
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
236
|
+
"required": True,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"name": "account_id",
|
|
240
|
+
"description": "AWS account ID (12-digit number)",
|
|
241
|
+
"required": True,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
"policy": {
|
|
245
|
+
"Version": "2012-10-17",
|
|
246
|
+
"Statement": [
|
|
247
|
+
{
|
|
248
|
+
"Sid": "DynamoDBTableAccess",
|
|
249
|
+
"Effect": "Allow",
|
|
250
|
+
"Action": [
|
|
251
|
+
"dynamodb:GetItem",
|
|
252
|
+
"dynamodb:PutItem",
|
|
253
|
+
"dynamodb:UpdateItem",
|
|
254
|
+
"dynamodb:DeleteItem",
|
|
255
|
+
"dynamodb:Query",
|
|
256
|
+
"dynamodb:Scan",
|
|
257
|
+
"dynamodb:BatchGetItem",
|
|
258
|
+
"dynamodb:BatchWriteItem",
|
|
259
|
+
],
|
|
260
|
+
"Resource": [
|
|
261
|
+
"arn:aws:dynamodb:${region}:${account_id}:table/${table_name}",
|
|
262
|
+
"arn:aws:dynamodb:${region}:${account_id}:table/${table_name}/index/*",
|
|
263
|
+
],
|
|
264
|
+
"Condition": {
|
|
265
|
+
"StringEquals": {"aws:ResourceTag/owner": "$${aws:PrincipalTag/owner}"},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"Sid": "DynamoDBDescribe",
|
|
270
|
+
"Effect": "Allow",
|
|
271
|
+
"Action": [
|
|
272
|
+
"dynamodb:DescribeTable",
|
|
273
|
+
"dynamodb:DescribeTimeToLive",
|
|
274
|
+
],
|
|
275
|
+
"Resource": "arn:aws:dynamodb:${region}:${account_id}:table/${table_name}",
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
"cloudwatch-logs": {
|
|
281
|
+
"name": "cloudwatch-logs",
|
|
282
|
+
"description": "CloudWatch Logs write permissions for application logging",
|
|
283
|
+
"variables": [
|
|
284
|
+
{
|
|
285
|
+
"name": "log_group_prefix",
|
|
286
|
+
"description": "Log group prefix (e.g., /aws/lambda/my-app or /app/production)",
|
|
287
|
+
"required": True,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"name": "region",
|
|
291
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
292
|
+
"required": True,
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"name": "account_id",
|
|
296
|
+
"description": "AWS account ID (12-digit number)",
|
|
297
|
+
"required": True,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
"policy": {
|
|
301
|
+
"Version": "2012-10-17",
|
|
302
|
+
"Statement": [
|
|
303
|
+
{
|
|
304
|
+
"Sid": "CloudWatchLogsWrite",
|
|
305
|
+
"Effect": "Allow",
|
|
306
|
+
"Action": [
|
|
307
|
+
"logs:CreateLogGroup",
|
|
308
|
+
"logs:CreateLogStream",
|
|
309
|
+
"logs:PutLogEvents",
|
|
310
|
+
"logs:DescribeLogGroups",
|
|
311
|
+
"logs:DescribeLogStreams",
|
|
312
|
+
],
|
|
313
|
+
"Resource": [
|
|
314
|
+
"arn:aws:logs:${region}:${account_id}:log-group:${log_group_prefix}:*",
|
|
315
|
+
"arn:aws:logs:${region}:${account_id}:log-group:${log_group_prefix}",
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
"secrets-manager-read": {
|
|
322
|
+
"name": "secrets-manager-read",
|
|
323
|
+
"description": "Read-only access to Secrets Manager secrets with prefix filtering",
|
|
324
|
+
"variables": [
|
|
325
|
+
{
|
|
326
|
+
"name": "secret_prefix",
|
|
327
|
+
"description": "Secret name prefix (e.g., app/production/ or database/)",
|
|
328
|
+
"required": True,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"name": "region",
|
|
332
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
333
|
+
"required": True,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"name": "account_id",
|
|
337
|
+
"description": "AWS account ID (12-digit number)",
|
|
338
|
+
"required": True,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
"policy": {
|
|
342
|
+
"Version": "2012-10-17",
|
|
343
|
+
"Statement": [
|
|
344
|
+
{
|
|
345
|
+
"Sid": "AllowSecretsManagerSameOwner",
|
|
346
|
+
"Effect": "Allow",
|
|
347
|
+
"Action": ["secretsmanager:*"],
|
|
348
|
+
"Resource": "arn:aws:secretsmanager:${region}:${account_id}:secret:${secret_prefix}*",
|
|
349
|
+
"Condition": {
|
|
350
|
+
"StringEquals": {
|
|
351
|
+
"aws:ResourceTag/owner": "$${aws:PrincipalTag/owner}",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
"Sid": "SecretsManagerRead",
|
|
357
|
+
"Effect": "Allow",
|
|
358
|
+
"Action": [
|
|
359
|
+
"secretsmanager:GetSecretValue",
|
|
360
|
+
"secretsmanager:DescribeSecret",
|
|
361
|
+
"secretsmanager:ListSecretVersionIds",
|
|
362
|
+
],
|
|
363
|
+
"Resource": "arn:aws:secretsmanager:${region}:${account_id}:secret:${secret_prefix}*",
|
|
364
|
+
"Condition": {
|
|
365
|
+
"StringEquals": {
|
|
366
|
+
"secretsmanager:VersionStage": "AWSCURRENT",
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"Sid": "SecretsManagerList",
|
|
372
|
+
"Effect": "Allow",
|
|
373
|
+
"Action": "secretsmanager:ListSecrets",
|
|
374
|
+
"Resource": "*",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"Sid": "DenyPolicyChanges",
|
|
378
|
+
"Effect": "Deny",
|
|
379
|
+
"Action": "secretsmanager:*Policy",
|
|
380
|
+
"Resource": "*",
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
"kms-encrypt-decrypt": {
|
|
386
|
+
"name": "kms-encrypt-decrypt",
|
|
387
|
+
"description": "KMS key encryption and decryption permissions",
|
|
388
|
+
"variables": [
|
|
389
|
+
{
|
|
390
|
+
"name": "key_id",
|
|
391
|
+
"description": "KMS key ID or ARN (e.g., 12345678-1234-1234-1234-123456789012 or full ARN)",
|
|
392
|
+
"required": True,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"name": "region",
|
|
396
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
397
|
+
"required": True,
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"name": "account_id",
|
|
401
|
+
"description": "AWS account ID (12-digit number)",
|
|
402
|
+
"required": True,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
"policy": {
|
|
406
|
+
"Version": "2012-10-17",
|
|
407
|
+
"Statement": [
|
|
408
|
+
{
|
|
409
|
+
"Sid": "KMSEncryptDecrypt",
|
|
410
|
+
"Effect": "Allow",
|
|
411
|
+
"Action": [
|
|
412
|
+
"kms:Decrypt",
|
|
413
|
+
"kms:Encrypt",
|
|
414
|
+
"kms:GenerateDataKey",
|
|
415
|
+
"kms:GenerateDataKeyWithoutPlaintext",
|
|
416
|
+
"kms:DescribeKey",
|
|
417
|
+
],
|
|
418
|
+
"Resource": "arn:aws:kms:${region}:${account_id}:key/${key_id}",
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
"ec2-describe": {
|
|
424
|
+
"name": "ec2-describe",
|
|
425
|
+
"description": "Read-only EC2 describe permissions for monitoring and discovery",
|
|
426
|
+
"variables": [], # No variables needed
|
|
427
|
+
"policy": {
|
|
428
|
+
"Version": "2012-10-17",
|
|
429
|
+
"Statement": [
|
|
430
|
+
{
|
|
431
|
+
"Sid": "EC2Describe",
|
|
432
|
+
"Effect": "Allow",
|
|
433
|
+
"Action": [
|
|
434
|
+
"ec2:DescribeInstances",
|
|
435
|
+
"ec2:DescribeImages",
|
|
436
|
+
"ec2:DescribeVolumes",
|
|
437
|
+
"ec2:DescribeSnapshots",
|
|
438
|
+
"ec2:DescribeSecurityGroups",
|
|
439
|
+
"ec2:DescribeVpcs",
|
|
440
|
+
"ec2:DescribeSubnets",
|
|
441
|
+
"ec2:DescribeNetworkInterfaces",
|
|
442
|
+
"ec2:DescribeRegions",
|
|
443
|
+
"ec2:DescribeAvailabilityZones",
|
|
444
|
+
],
|
|
445
|
+
"Resource": "*",
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
"ecs-task-execution": {
|
|
451
|
+
"name": "ecs-task-execution",
|
|
452
|
+
"description": "ECS task execution role with ECR and CloudWatch Logs access",
|
|
453
|
+
"variables": [
|
|
454
|
+
{
|
|
455
|
+
"name": "account_id",
|
|
456
|
+
"description": "AWS account ID (12-digit number)",
|
|
457
|
+
"required": True,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"name": "region",
|
|
461
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
462
|
+
"required": True,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
"policy": {
|
|
466
|
+
"Version": "2012-10-17",
|
|
467
|
+
"Statement": [
|
|
468
|
+
{
|
|
469
|
+
"Sid": "ECRImageAccess",
|
|
470
|
+
"Effect": "Allow",
|
|
471
|
+
"Action": [
|
|
472
|
+
"ecr:GetAuthorizationToken",
|
|
473
|
+
"ecr:BatchCheckLayerAvailability",
|
|
474
|
+
"ecr:GetDownloadUrlForLayer",
|
|
475
|
+
"ecr:BatchGetImage",
|
|
476
|
+
],
|
|
477
|
+
"Resource": "*",
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"Sid": "CloudWatchLogsAccess",
|
|
481
|
+
"Effect": "Allow",
|
|
482
|
+
"Action": [
|
|
483
|
+
"logs:CreateLogGroup",
|
|
484
|
+
"logs:CreateLogStream",
|
|
485
|
+
"logs:PutLogEvents",
|
|
486
|
+
],
|
|
487
|
+
"Resource": "arn:aws:logs:${region}:${account_id}:log-group:/ecs/*:*",
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"Sid": "SecretsManagerAccess",
|
|
491
|
+
"Effect": "Allow",
|
|
492
|
+
"Action": [
|
|
493
|
+
"secretsmanager:GetSecretValue",
|
|
494
|
+
],
|
|
495
|
+
"Resource": "arn:aws:secretsmanager:${region}:${account_id}:secret:ecs/*",
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"Sid": "SSMParameterAccess",
|
|
499
|
+
"Effect": "Allow",
|
|
500
|
+
"Action": [
|
|
501
|
+
"ssm:GetParameters",
|
|
502
|
+
"ssm:GetParameter",
|
|
503
|
+
],
|
|
504
|
+
"Resource": "arn:aws:ssm:${region}:${account_id}:parameter/ecs/*",
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
"sqs-consumer": {
|
|
510
|
+
"name": "sqs-consumer",
|
|
511
|
+
"description": "SQS queue consumer permissions for receiving and processing messages",
|
|
512
|
+
"variables": [
|
|
513
|
+
{
|
|
514
|
+
"name": "queue_name",
|
|
515
|
+
"description": "SQS queue name",
|
|
516
|
+
"required": True,
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"name": "region",
|
|
520
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
521
|
+
"required": True,
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"name": "account_id",
|
|
525
|
+
"description": "AWS account ID (12-digit number)",
|
|
526
|
+
"required": True,
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
"policy": {
|
|
530
|
+
"Version": "2012-10-17",
|
|
531
|
+
"Statement": [
|
|
532
|
+
{
|
|
533
|
+
"Sid": "SQSConsumerAccess",
|
|
534
|
+
"Effect": "Allow",
|
|
535
|
+
"Action": [
|
|
536
|
+
"sqs:ReceiveMessage",
|
|
537
|
+
"sqs:DeleteMessage",
|
|
538
|
+
"sqs:ChangeMessageVisibility",
|
|
539
|
+
"sqs:GetQueueAttributes",
|
|
540
|
+
],
|
|
541
|
+
"Resource": "arn:aws:sqs:${region}:${account_id}:${queue_name}",
|
|
542
|
+
"Condition": {
|
|
543
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
"sns-publisher": {
|
|
550
|
+
"name": "sns-publisher",
|
|
551
|
+
"description": "SNS topic publisher permissions for sending notifications",
|
|
552
|
+
"variables": [
|
|
553
|
+
{
|
|
554
|
+
"name": "topic_name",
|
|
555
|
+
"description": "SNS topic name",
|
|
556
|
+
"required": True,
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"name": "region",
|
|
560
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
561
|
+
"required": True,
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"name": "account_id",
|
|
565
|
+
"description": "AWS account ID (12-digit number)",
|
|
566
|
+
"required": True,
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
"policy": {
|
|
570
|
+
"Version": "2012-10-17",
|
|
571
|
+
"Statement": [
|
|
572
|
+
{
|
|
573
|
+
"Sid": "SNSPublishAccess",
|
|
574
|
+
"Effect": "Allow",
|
|
575
|
+
"Action": [
|
|
576
|
+
"sns:Publish",
|
|
577
|
+
"sns:GetTopicAttributes",
|
|
578
|
+
],
|
|
579
|
+
"Resource": "arn:aws:sns:${region}:${account_id}:${topic_name}",
|
|
580
|
+
"Condition": {
|
|
581
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
],
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
"step-functions-execution": {
|
|
588
|
+
"name": "step-functions-execution",
|
|
589
|
+
"description": "Step Functions state machine execution permissions",
|
|
590
|
+
"variables": [
|
|
591
|
+
{
|
|
592
|
+
"name": "state_machine_name",
|
|
593
|
+
"description": "Step Functions state machine name",
|
|
594
|
+
"required": True,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
"name": "region",
|
|
598
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
599
|
+
"required": True,
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"name": "account_id",
|
|
603
|
+
"description": "AWS account ID (12-digit number)",
|
|
604
|
+
"required": True,
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
"policy": {
|
|
608
|
+
"Version": "2012-10-17",
|
|
609
|
+
"Statement": [
|
|
610
|
+
{
|
|
611
|
+
"Sid": "StepFunctionsStartExecution",
|
|
612
|
+
"Effect": "Allow",
|
|
613
|
+
"Action": "states:StartExecution",
|
|
614
|
+
"Resource": "arn:aws:states:${region}:${account_id}:stateMachine:${state_machine_name}",
|
|
615
|
+
"Condition": {
|
|
616
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"Sid": "StepFunctionsManageExecutions",
|
|
621
|
+
"Effect": "Allow",
|
|
622
|
+
"Action": [
|
|
623
|
+
"states:DescribeExecution",
|
|
624
|
+
"states:GetExecutionHistory",
|
|
625
|
+
"states:StopExecution",
|
|
626
|
+
],
|
|
627
|
+
"Resource": "arn:aws:states:${region}:${account_id}:execution:${state_machine_name}:*",
|
|
628
|
+
"Condition": {
|
|
629
|
+
"StringEquals": {"aws:ResourceAccount": "$${aws:PrincipalAccount}"},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
"Sid": "StepFunctionsDescribe",
|
|
634
|
+
"Effect": "Allow",
|
|
635
|
+
"Action": [
|
|
636
|
+
"states:DescribeStateMachine",
|
|
637
|
+
"states:ListExecutions",
|
|
638
|
+
],
|
|
639
|
+
"Resource": "arn:aws:states:${region}:${account_id}:stateMachine:${state_machine_name}",
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
"api-gateway-invoke": {
|
|
645
|
+
"name": "api-gateway-invoke",
|
|
646
|
+
"description": "API Gateway invoke permissions for executing API methods",
|
|
647
|
+
"variables": [
|
|
648
|
+
{
|
|
649
|
+
"name": "api_id",
|
|
650
|
+
"description": "API Gateway REST API ID",
|
|
651
|
+
"required": True,
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
"name": "stage",
|
|
655
|
+
"description": "API Gateway stage name (e.g., prod, dev, test)",
|
|
656
|
+
"required": True,
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
"name": "region",
|
|
660
|
+
"description": "AWS region (e.g., us-east-1, us-west-2)",
|
|
661
|
+
"required": True,
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"name": "account_id",
|
|
665
|
+
"description": "AWS account ID (12-digit number)",
|
|
666
|
+
"required": True,
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
"policy": {
|
|
670
|
+
"Version": "2012-10-17",
|
|
671
|
+
"Statement": [
|
|
672
|
+
{
|
|
673
|
+
"Sid": "APIGatewayInvoke",
|
|
674
|
+
"Effect": "Allow",
|
|
675
|
+
"Action": "execute-api:Invoke",
|
|
676
|
+
"Resource": "arn:aws:execute-api:${region}:${account_id}:${api_id}/${stage}/*/*",
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
"cross-account-assume-role": {
|
|
682
|
+
"name": "cross-account-assume-role",
|
|
683
|
+
"description": "Trust policy for cross-account role assumption with MFA and external ID security",
|
|
684
|
+
"variables": [
|
|
685
|
+
{
|
|
686
|
+
"name": "trusted_account_id",
|
|
687
|
+
"description": "AWS account ID that is trusted to assume this role",
|
|
688
|
+
"required": True,
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"name": "external_id",
|
|
692
|
+
"description": "External ID for additional security (recommended for third-party access)",
|
|
693
|
+
"required": True,
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
"policy": {
|
|
697
|
+
"Version": "2012-10-17",
|
|
698
|
+
"Statement": [
|
|
699
|
+
{
|
|
700
|
+
"Sid": "CrossAccountAssumeRole",
|
|
701
|
+
"Effect": "Allow",
|
|
702
|
+
"Principal": {
|
|
703
|
+
"AWS": "arn:aws:iam::${trusted_account_id}:root",
|
|
704
|
+
},
|
|
705
|
+
"Action": "sts:AssumeRole",
|
|
706
|
+
"Condition": {
|
|
707
|
+
"StringEquals": {
|
|
708
|
+
"sts:ExternalId": "${external_id}",
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
# ============================================================================
|
|
719
|
+
# Helper Functions
|
|
720
|
+
# ============================================================================
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def get_template(name: str) -> dict[str, Any] | None:
|
|
724
|
+
"""Get a template definition by name.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
name: Template name (e.g., "s3-read-only")
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
Template definition dict with name, description, variables, and policy.
|
|
731
|
+
Returns None if template not found.
|
|
732
|
+
|
|
733
|
+
Example:
|
|
734
|
+
>>> template = get_template("s3-read-only")
|
|
735
|
+
>>> if template:
|
|
736
|
+
... print(template["description"])
|
|
737
|
+
... print(template["variables"])
|
|
738
|
+
"""
|
|
739
|
+
return TEMPLATES.get(name)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def list_templates() -> list[dict[str, Any]]:
|
|
743
|
+
"""List all available templates with their metadata.
|
|
744
|
+
|
|
745
|
+
Returns:
|
|
746
|
+
List of dicts containing name, description, and variables for each template.
|
|
747
|
+
Does not include the full policy definition.
|
|
748
|
+
|
|
749
|
+
Example:
|
|
750
|
+
>>> templates = list_templates()
|
|
751
|
+
>>> for t in templates:
|
|
752
|
+
... print(f"{t['name']}: {t['description']}")
|
|
753
|
+
... for var in t['variables']:
|
|
754
|
+
... print(f" - {var['name']}: {var['description']}")
|
|
755
|
+
"""
|
|
756
|
+
result = []
|
|
757
|
+
for template_name, template_def in TEMPLATES.items():
|
|
758
|
+
result.append(
|
|
759
|
+
{
|
|
760
|
+
"name": template_def["name"],
|
|
761
|
+
"description": template_def["description"],
|
|
762
|
+
"variables": template_def["variables"],
|
|
763
|
+
}
|
|
764
|
+
)
|
|
765
|
+
return result
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def render_template(name: str, variables: dict[str, str]) -> dict[str, Any]:
|
|
769
|
+
"""Render a template by substituting variables.
|
|
770
|
+
|
|
771
|
+
This function takes a template name and variable values, then performs
|
|
772
|
+
string substitution to generate a complete IAM policy document.
|
|
773
|
+
|
|
774
|
+
Variable substitution uses Python's string.Template with ${variable} syntax.
|
|
775
|
+
For optional variables with default values, the default is used if not provided.
|
|
776
|
+
|
|
777
|
+
Args:
|
|
778
|
+
name: Template name (e.g., "s3-read-only")
|
|
779
|
+
variables: Dictionary mapping variable names to values
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
Rendered policy document as a dictionary
|
|
783
|
+
|
|
784
|
+
Raises:
|
|
785
|
+
ValueError: If template not found or required variables missing
|
|
786
|
+
KeyError: If template substitution fails due to missing variables
|
|
787
|
+
|
|
788
|
+
Example:
|
|
789
|
+
>>> policy = render_template("s3-read-only", {
|
|
790
|
+
... "bucket_name": "my-data-bucket",
|
|
791
|
+
... "prefix": "reports/"
|
|
792
|
+
... })
|
|
793
|
+
>>> print(policy["Statement"][0]["Resource"])
|
|
794
|
+
'arn:aws:s3:::my-data-bucket'
|
|
795
|
+
|
|
796
|
+
Notes:
|
|
797
|
+
- Empty string prefixes are normalized to "" in resource ARNs
|
|
798
|
+
- All variable values are treated as strings
|
|
799
|
+
- Resource ARNs with ${prefix} handle trailing slashes automatically
|
|
800
|
+
"""
|
|
801
|
+
# Get template definition
|
|
802
|
+
template_def = get_template(name)
|
|
803
|
+
if not template_def:
|
|
804
|
+
available = ", ".join(TEMPLATES.keys())
|
|
805
|
+
raise ValueError(f"Template '{name}' not found. Available templates: {available}")
|
|
806
|
+
|
|
807
|
+
# Build final variables dict with defaults
|
|
808
|
+
# First, collect all missing required variables for a comprehensive error message
|
|
809
|
+
missing_vars = []
|
|
810
|
+
for var_def in template_def["variables"]:
|
|
811
|
+
var_name = var_def["name"]
|
|
812
|
+
if var_def["required"] and var_name not in variables:
|
|
813
|
+
missing_vars.append(
|
|
814
|
+
{"name": var_name, "description": var_def.get("description", "No description")}
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
if missing_vars:
|
|
818
|
+
missing_details = ", ".join(f"'{v['name']}' ({v['description']})" for v in missing_vars)
|
|
819
|
+
raise ValueError(
|
|
820
|
+
f"Missing {len(missing_vars)} required variable(s) for template '{name}': {missing_details}"
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
final_vars = {}
|
|
824
|
+
for var_def in template_def["variables"]:
|
|
825
|
+
var_name = var_def["name"]
|
|
826
|
+
# Use provided value or default
|
|
827
|
+
final_vars[var_name] = variables.get(var_name, var_def.get("default", ""))
|
|
828
|
+
|
|
829
|
+
# Convert policy to JSON string for template substitution
|
|
830
|
+
import json
|
|
831
|
+
|
|
832
|
+
policy_json = json.dumps(template_def["policy"])
|
|
833
|
+
|
|
834
|
+
# Substitute variables using string.Template
|
|
835
|
+
template = Template(policy_json)
|
|
836
|
+
try:
|
|
837
|
+
rendered_json = template.substitute(final_vars)
|
|
838
|
+
except KeyError as e:
|
|
839
|
+
raise ValueError(f"Template substitution failed for '{name}': missing variable {e}") from e
|
|
840
|
+
|
|
841
|
+
# Parse back to dict
|
|
842
|
+
rendered_policy = json.loads(rendered_json)
|
|
843
|
+
|
|
844
|
+
return rendered_policy
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
# ============================================================================
|
|
848
|
+
# Exports
|
|
849
|
+
# ============================================================================
|
|
850
|
+
|
|
851
|
+
__all__ = [
|
|
852
|
+
"TEMPLATES",
|
|
853
|
+
"get_template",
|
|
854
|
+
"list_templates",
|
|
855
|
+
"render_template",
|
|
856
|
+
]
|