awslabs.eks-mcp-server 0.1.1__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.
- awslabs/__init__.py +13 -0
- awslabs/eks_mcp_server/__init__.py +14 -0
- awslabs/eks_mcp_server/aws_helper.py +71 -0
- awslabs/eks_mcp_server/cloudwatch_handler.py +670 -0
- awslabs/eks_mcp_server/consts.py +33 -0
- awslabs/eks_mcp_server/eks_kb_handler.py +86 -0
- awslabs/eks_mcp_server/eks_stack_handler.py +661 -0
- awslabs/eks_mcp_server/iam_handler.py +359 -0
- awslabs/eks_mcp_server/k8s_apis.py +506 -0
- awslabs/eks_mcp_server/k8s_client_cache.py +164 -0
- awslabs/eks_mcp_server/k8s_handler.py +1151 -0
- awslabs/eks_mcp_server/logging_helper.py +52 -0
- awslabs/eks_mcp_server/models.py +271 -0
- awslabs/eks_mcp_server/server.py +151 -0
- awslabs/eks_mcp_server/templates/eks-templates/eks-with-vpc.yaml +454 -0
- awslabs/eks_mcp_server/templates/k8s-templates/deployment.yaml +49 -0
- awslabs/eks_mcp_server/templates/k8s-templates/service.yaml +18 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/METADATA +596 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/RECORD +23 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
- awslabs_eks_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
|
4
|
+
# with the License. A copy of the License is located at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
|
|
9
|
+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
|
|
10
|
+
# and limitations under the License.
|
|
11
|
+
|
|
12
|
+
"""IAM handler for the EKS MCP Server."""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from awslabs.eks_mcp_server.aws_helper import AwsHelper
|
|
16
|
+
from awslabs.eks_mcp_server.logging_helper import LogLevel, log_with_request_id
|
|
17
|
+
from awslabs.eks_mcp_server.models import (
|
|
18
|
+
AddInlinePolicyResponse,
|
|
19
|
+
PolicySummary,
|
|
20
|
+
RoleDescriptionResponse,
|
|
21
|
+
)
|
|
22
|
+
from mcp.server.fastmcp import Context
|
|
23
|
+
from mcp.types import TextContent
|
|
24
|
+
from pydantic import Field
|
|
25
|
+
from typing import Any, Dict, List, Union
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IAMHandler:
|
|
29
|
+
"""Handler for AWS IAM operations in the EKS MCP Server.
|
|
30
|
+
|
|
31
|
+
This class provides tools for managing IAM roles and policies, including
|
|
32
|
+
describing roles with their attached policies and adding inline permissions
|
|
33
|
+
to policies.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, mcp, allow_write: bool = False):
|
|
37
|
+
"""Initialize the IAM handler.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
mcp: The MCP server instance
|
|
41
|
+
allow_write: Whether to enable write access (default: False)
|
|
42
|
+
"""
|
|
43
|
+
self.mcp = mcp
|
|
44
|
+
self.iam_client = AwsHelper.create_boto3_client('iam')
|
|
45
|
+
self.allow_write = allow_write
|
|
46
|
+
|
|
47
|
+
# Register tools
|
|
48
|
+
self.mcp.tool(name='add_inline_policy')(self.add_inline_policy)
|
|
49
|
+
self.mcp.tool(name='get_policies_for_role')(self.get_policies_for_role)
|
|
50
|
+
|
|
51
|
+
async def get_policies_for_role(
|
|
52
|
+
self,
|
|
53
|
+
ctx: Context,
|
|
54
|
+
role_name: str = Field(
|
|
55
|
+
...,
|
|
56
|
+
description='Name of the IAM role to get policies for. The role must exist in your AWS account.',
|
|
57
|
+
),
|
|
58
|
+
) -> RoleDescriptionResponse:
|
|
59
|
+
"""Get all policies attached to an IAM role.
|
|
60
|
+
|
|
61
|
+
This tool retrieves all policies associated with an IAM role, providing a comprehensive view
|
|
62
|
+
of the role's permissions and trust relationships. It helps you understand the current
|
|
63
|
+
permissions, identify missing or excessive permissions, troubleshoot EKS cluster issues,
|
|
64
|
+
and verify trust relationships for service roles.
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
- The role must exist in your AWS account
|
|
68
|
+
- Valid AWS credentials with permissions to read IAM role information
|
|
69
|
+
|
|
70
|
+
## Response Information
|
|
71
|
+
The response includes role ARN, assume role policy document (trust relationships),
|
|
72
|
+
role description, managed policies with their documents, and inline policies with
|
|
73
|
+
their documents.
|
|
74
|
+
|
|
75
|
+
## Usage Tips
|
|
76
|
+
- Use this tool before adding new permissions to understand existing access
|
|
77
|
+
- Check the assume role policy to verify which services or roles can assume this role
|
|
78
|
+
- Look for overly permissive policies that might pose security risks
|
|
79
|
+
- Use with add_inline_policy to implement least-privilege permissions
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
ctx: The MCP context
|
|
83
|
+
role_name: Name of the IAM role to get policies for
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
RoleDescriptionResponse: Detailed information about the role's policies
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
log_with_request_id(ctx, LogLevel.INFO, f'Describing IAM role: {role_name}')
|
|
90
|
+
|
|
91
|
+
# Get role details
|
|
92
|
+
role_response = self.iam_client.get_role(RoleName=role_name)
|
|
93
|
+
role = role_response['Role']
|
|
94
|
+
|
|
95
|
+
# Get attached managed policies
|
|
96
|
+
managed_policies = self._get_managed_policies(ctx, role_name)
|
|
97
|
+
|
|
98
|
+
# Get inline policies
|
|
99
|
+
inline_policies = self._get_inline_policies(ctx, role_name)
|
|
100
|
+
|
|
101
|
+
# Parse the assume role policy document if it's a string, otherwise use it directly
|
|
102
|
+
if isinstance(role['AssumeRolePolicyDocument'], str):
|
|
103
|
+
assume_role_policy_document = json.loads(role['AssumeRolePolicyDocument'])
|
|
104
|
+
else:
|
|
105
|
+
assume_role_policy_document = role['AssumeRolePolicyDocument']
|
|
106
|
+
|
|
107
|
+
# Create the response
|
|
108
|
+
return RoleDescriptionResponse(
|
|
109
|
+
isError=False,
|
|
110
|
+
content=[
|
|
111
|
+
TextContent(
|
|
112
|
+
type='text',
|
|
113
|
+
text=f'Successfully retrieved details for IAM role: {role_name}',
|
|
114
|
+
)
|
|
115
|
+
],
|
|
116
|
+
role_arn=role['Arn'],
|
|
117
|
+
assume_role_policy_document=assume_role_policy_document,
|
|
118
|
+
description=role.get('Description'),
|
|
119
|
+
managed_policies=managed_policies,
|
|
120
|
+
inline_policies=inline_policies,
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
error_message = f'Failed to describe IAM role: {str(e)}'
|
|
124
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
125
|
+
|
|
126
|
+
# Return a response with error status
|
|
127
|
+
return RoleDescriptionResponse(
|
|
128
|
+
isError=True,
|
|
129
|
+
content=[TextContent(type='text', text=error_message)],
|
|
130
|
+
role_arn='',
|
|
131
|
+
assume_role_policy_document={},
|
|
132
|
+
description=None,
|
|
133
|
+
managed_policies=[],
|
|
134
|
+
inline_policies=[],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def add_inline_policy(
|
|
138
|
+
self,
|
|
139
|
+
ctx: Context,
|
|
140
|
+
policy_name: str = Field(
|
|
141
|
+
..., description='Name of the inline policy to create. Must be unique within the role.'
|
|
142
|
+
),
|
|
143
|
+
role_name: str = Field(
|
|
144
|
+
..., description='Name of the IAM role to add the policy to. The role must exist.'
|
|
145
|
+
),
|
|
146
|
+
permissions: Union[Dict[str, Any], List[Dict[str, Any]]] = Field(
|
|
147
|
+
...,
|
|
148
|
+
description="""Permissions to include in the policy as IAM policy statements in JSON format.
|
|
149
|
+
Can be either a single statement object or an array of statement objects.""",
|
|
150
|
+
),
|
|
151
|
+
) -> AddInlinePolicyResponse:
|
|
152
|
+
"""Add a new inline policy to an IAM role.
|
|
153
|
+
|
|
154
|
+
This tool creates a new inline policy with the specified permissions and adds it to an IAM role.
|
|
155
|
+
Inline policies are embedded within the role and cannot be attached to multiple roles. Commonly used
|
|
156
|
+
for granting EKS clusters access to AWS services, enabling worker nodes to access resources, and
|
|
157
|
+
configuring permissions for CloudWatch logging and ECR access.
|
|
158
|
+
|
|
159
|
+
## Requirements
|
|
160
|
+
- The server must be run with the `--allow-write` flag
|
|
161
|
+
- The role must exist in your AWS account
|
|
162
|
+
- The policy name must be unique within the role
|
|
163
|
+
- You cannot modify existing policies with this tool
|
|
164
|
+
|
|
165
|
+
## Permission Format
|
|
166
|
+
The permissions parameter can be either a single policy statement or a list of statements.
|
|
167
|
+
|
|
168
|
+
### Single Statement Example
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"Effect": "Allow",
|
|
172
|
+
"Action": ["s3:GetObject", "s3:PutObject"],
|
|
173
|
+
"Resource": "arn:aws:s3:::example-bucket/*"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Usage Tips
|
|
178
|
+
- Follow the principle of least privilege by granting only necessary permissions
|
|
179
|
+
- Use specific resources rather than "*" whenever possible
|
|
180
|
+
- Consider using conditions to further restrict permissions
|
|
181
|
+
- Group related permissions into logical policies with descriptive names
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
ctx: The MCP context
|
|
185
|
+
policy_name: Name of the new inline policy to create
|
|
186
|
+
role_name: Name of the role to add the policy to
|
|
187
|
+
permissions: Permissions to include in the policy (in JSON format)
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
AddInlinePolicyResponse: Information about the created policy
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
# Check if write access is disabled
|
|
194
|
+
if not self.allow_write:
|
|
195
|
+
error_message = 'Adding inline policies requires --allow-write flag'
|
|
196
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
197
|
+
return AddInlinePolicyResponse(
|
|
198
|
+
isError=True,
|
|
199
|
+
content=[TextContent(type='text', text=error_message)],
|
|
200
|
+
policy_name=policy_name,
|
|
201
|
+
role_name=role_name,
|
|
202
|
+
permissions_added={},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Create the inline policy
|
|
206
|
+
return self._create_inline_policy(ctx, role_name, policy_name, permissions)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
error_message = f'Failed to create inline policy: {str(e)}'
|
|
210
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
211
|
+
|
|
212
|
+
# Return a response with error status
|
|
213
|
+
return AddInlinePolicyResponse(
|
|
214
|
+
isError=True,
|
|
215
|
+
content=[TextContent(type='text', text=error_message)],
|
|
216
|
+
policy_name=policy_name,
|
|
217
|
+
role_name=role_name,
|
|
218
|
+
permissions_added={},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def _get_managed_policies(self, ctx, role_name):
|
|
222
|
+
"""Get managed policies attached to a role.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
ctx: The MCP context
|
|
226
|
+
role_name: Name of the IAM role
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of PolicySummary objects
|
|
230
|
+
"""
|
|
231
|
+
managed_policies = []
|
|
232
|
+
managed_policies_response = self.iam_client.list_attached_role_policies(RoleName=role_name)
|
|
233
|
+
|
|
234
|
+
for policy in managed_policies_response.get('AttachedPolicies', []):
|
|
235
|
+
policy_arn = policy['PolicyArn']
|
|
236
|
+
policy_details = self.iam_client.get_policy(PolicyArn=policy_arn)['Policy']
|
|
237
|
+
|
|
238
|
+
# Get the policy version details to get the policy document
|
|
239
|
+
policy_version = None
|
|
240
|
+
try:
|
|
241
|
+
policy_version_response = self.iam_client.get_policy_version(
|
|
242
|
+
PolicyArn=policy_arn, VersionId=policy_details.get('DefaultVersionId', 'v1')
|
|
243
|
+
)
|
|
244
|
+
policy_version = policy_version_response.get('PolicyVersion', {})
|
|
245
|
+
except Exception as e:
|
|
246
|
+
log_with_request_id(
|
|
247
|
+
ctx, LogLevel.WARNING, f'Failed to get policy version: {str(e)}'
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
managed_policies.append(
|
|
251
|
+
PolicySummary(
|
|
252
|
+
policy_type='Managed',
|
|
253
|
+
description=policy_details.get('Description'),
|
|
254
|
+
policy_document=policy_version.get('Document') if policy_version else None,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return managed_policies
|
|
259
|
+
|
|
260
|
+
def _get_inline_policies(self, ctx, role_name):
|
|
261
|
+
"""Get inline policies embedded in a role.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
ctx: The MCP context
|
|
265
|
+
role_name: Name of the IAM role
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
List of PolicySummary objects
|
|
269
|
+
"""
|
|
270
|
+
inline_policies = []
|
|
271
|
+
inline_policies_response = self.iam_client.list_role_policies(RoleName=role_name)
|
|
272
|
+
|
|
273
|
+
for policy_name in inline_policies_response.get('PolicyNames', []):
|
|
274
|
+
policy_response = self.iam_client.get_role_policy(
|
|
275
|
+
RoleName=role_name, PolicyName=policy_name
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
inline_policies.append(
|
|
279
|
+
PolicySummary(
|
|
280
|
+
policy_type='Inline',
|
|
281
|
+
description=None,
|
|
282
|
+
policy_document=policy_response.get('PolicyDocument'),
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return inline_policies
|
|
287
|
+
|
|
288
|
+
def _create_inline_policy(self, ctx, role_name, policy_name, permissions):
|
|
289
|
+
"""Create a new inline policy with the specified permissions.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
ctx: The MCP context
|
|
293
|
+
role_name: Name of the role
|
|
294
|
+
policy_name: Name of the new policy to create
|
|
295
|
+
permissions: Permissions to include in the policy
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
AddInlinePolicyResponse: Information about the created policy
|
|
299
|
+
"""
|
|
300
|
+
log_with_request_id(
|
|
301
|
+
ctx,
|
|
302
|
+
LogLevel.INFO,
|
|
303
|
+
f'Creating new inline policy {policy_name} in role {role_name}',
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Check if the policy already exists
|
|
307
|
+
try:
|
|
308
|
+
self.iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
|
|
309
|
+
# If we get here, the policy exists
|
|
310
|
+
error_message = f'Policy {policy_name} already exists in role {role_name}. Cannot modify existing policies.'
|
|
311
|
+
log_with_request_id(ctx, LogLevel.ERROR, error_message)
|
|
312
|
+
return AddInlinePolicyResponse(
|
|
313
|
+
isError=True,
|
|
314
|
+
content=[TextContent(type='text', text=error_message)],
|
|
315
|
+
policy_name=policy_name,
|
|
316
|
+
role_name=role_name,
|
|
317
|
+
permissions_added={},
|
|
318
|
+
)
|
|
319
|
+
except self.iam_client.exceptions.NoSuchEntityException:
|
|
320
|
+
# Policy doesn't exist, we can create it
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
# Create a new policy document
|
|
324
|
+
policy_document = {'Version': '2012-10-17', 'Statement': []}
|
|
325
|
+
|
|
326
|
+
# Add the permissions to the policy document
|
|
327
|
+
self._add_permissions_to_document(policy_document, permissions)
|
|
328
|
+
|
|
329
|
+
# Create the policy
|
|
330
|
+
self.iam_client.put_role_policy(
|
|
331
|
+
RoleName=role_name, PolicyName=policy_name, PolicyDocument=json.dumps(policy_document)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
return AddInlinePolicyResponse(
|
|
335
|
+
isError=False,
|
|
336
|
+
content=[
|
|
337
|
+
TextContent(
|
|
338
|
+
type='text',
|
|
339
|
+
text=f'Successfully created new inline policy {policy_name} in role {role_name}',
|
|
340
|
+
)
|
|
341
|
+
],
|
|
342
|
+
policy_name=policy_name,
|
|
343
|
+
role_name=role_name,
|
|
344
|
+
permissions_added=permissions,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def _add_permissions_to_document(self, policy_document, permissions):
|
|
348
|
+
"""Add permissions to a policy document.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
policy_document: Policy document to modify
|
|
352
|
+
permissions: Permissions to add
|
|
353
|
+
"""
|
|
354
|
+
if isinstance(permissions, dict):
|
|
355
|
+
# Single statement
|
|
356
|
+
policy_document['Statement'].append(permissions)
|
|
357
|
+
elif isinstance(permissions, list):
|
|
358
|
+
# Multiple statements
|
|
359
|
+
policy_document['Statement'].extend(permissions)
|