Skip to content

Commit 0aa1a79

Browse files
authored
Add assume-role-arn option to update-kubeconfig command for cross-account access (#9443)
1 parent c533123 commit 0aa1a79

File tree

4 files changed

+125
-22
lines changed

4 files changed

+125
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "``eks``",
4+
"description": "Add assume-role-arn option to update-kubeconfig command for cross-account access"
5+
}

awscli/customizations/eks/update_kubeconfig.py

+44-21
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ class UpdateKubeconfigCommand(BasicCommand):
103103
'help_text': ("Alias for the generated user name. "
104104
"Defaults to match cluster ARN."),
105105
'required': False
106+
},
107+
{
108+
'name': 'assume-role-arn',
109+
'help_text': ('To assume a role for retrieving cluster information, '
110+
'specify an IAM role ARN with this option. '
111+
'Use this for cross-account access to get cluster details '
112+
'from the account where the cluster resides.'),
113+
'required': False
106114
}
107115
]
108116

@@ -249,27 +257,42 @@ def cluster_description(self):
249257
Cache the response in self._cluster_description.
250258
describe-cluster will only be called once.
251259
"""
252-
if self._cluster_description is None:
253-
if self._parsed_globals is None:
254-
client = self._session.create_client("eks")
255-
else:
256-
client = self._session.create_client(
257-
"eks",
258-
region_name=self._parsed_globals.region,
259-
endpoint_url=self._parsed_globals.endpoint_url,
260-
verify=self._parsed_globals.verify_ssl
261-
)
262-
full_description = client.describe_cluster(name=self._cluster_name)
263-
self._cluster_description = full_description["cluster"]
264-
265-
if "status" not in self._cluster_description:
266-
raise EKSClusterError("Cluster not found")
267-
if self._cluster_description["status"] not in ["ACTIVE", "UPDATING"]:
268-
raise EKSClusterError("Cluster status is {0}".format(
269-
self._cluster_description["status"]
270-
))
271-
272-
return self._cluster_description
260+
if self._cluster_description is not None:
261+
return self._cluster_description
262+
263+
client_kwargs = {}
264+
if self._parsed_globals:
265+
client_kwargs.update({
266+
"region_name": self._parsed_globals.region,
267+
"endpoint_url": self._parsed_globals.endpoint_url,
268+
"verify": self._parsed_globals.verify_ssl,
269+
})
270+
271+
# Handle role assumption if needed
272+
if getattr(self._parsed_args, 'assume_role_arn', None):
273+
sts_client = self._session.create_client('sts')
274+
credentials = sts_client.assume_role(
275+
RoleArn=self._parsed_args.assume_role_arn,
276+
RoleSessionName='EKSDescribeClusterSession'
277+
)["Credentials"]
278+
279+
client_kwargs.update({
280+
"aws_access_key_id": credentials["AccessKeyId"],
281+
"aws_secret_access_key": credentials["SecretAccessKey"],
282+
"aws_session_token": credentials["SessionToken"],
283+
})
284+
285+
client = self._session.create_client("eks", **client_kwargs)
286+
full_description = client.describe_cluster(name=self._cluster_name)
287+
cluster = full_description.get("cluster")
288+
289+
if not cluster or "status" not in cluster:
290+
raise EKSClusterError("Cluster not found")
291+
if cluster["status"] not in ["ACTIVE", "UPDATING"]:
292+
raise EKSClusterError(f"Cluster status is {cluster['status']}")
293+
294+
self._cluster_description = cluster
295+
return cluster
273296

274297
def get_cluster_entry(self):
275298
"""

tests/functional/eks/test_update_kubeconfig.py

+67-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
KubeconfigInaccessableError)
3333
from tests.functional.eks.test_util import (describe_cluster_response,
3434
describe_cluster_creating_response,
35-
get_testdata)
35+
get_testdata,
36+
assume_role_response)
3637

3738
def sanitize_output(output):
3839
"""
@@ -66,6 +67,15 @@ def setUp(self):
6667
self.client.describe_cluster.return_value = describe_cluster_response()
6768
self.mock_create_client.return_value = self.client
6869

70+
# Set up the sts_client_mock
71+
self.sts_client_mock = mock.Mock()
72+
self.sts_client_mock.assume_role.return_value = assume_role_response()
73+
74+
# Ensure the mock_create_client correctly returns the appropriate mock
75+
self.mock_create_client.side_effect = lambda service_name, **kwargs: (
76+
self.sts_client_mock if service_name == "sts" else self.client
77+
)
78+
6979
self.command = UpdateKubeconfigCommand(self.session)
7080
self.maxDiff = None
7181

@@ -422,3 +432,59 @@ def test_update_old_api_version(self):
422432

423433
self.assert_cmd(configs, passed, environment)
424434
self.assert_config_state("valid_old_api_version", "valid_old_api_version_updated")
435+
436+
def test_assume_role(self):
437+
"""
438+
Test that assume_role_arn is handled correctly when provided.
439+
"""
440+
configs = ["valid_existing"]
441+
self.initialize_tempfiles(configs)
442+
443+
# Include the --assume-role-arn argument
444+
args = [
445+
"--name", "ExampleCluster",
446+
"--assume-role-arn", "arn:aws:iam::123456789012:role/test-role"
447+
]
448+
449+
# Mock environment variables and paths
450+
kubeconfig_path = self._get_temp_config("valid_existing")
451+
default_path = self._get_temp_config("default_temp")
452+
453+
with mock.patch.dict(os.environ, {'KUBECONFIG': kubeconfig_path}):
454+
with mock.patch("awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH", default_path):
455+
self.command(args, None)
456+
457+
# Verify that assume_role was called with the correct parameters
458+
self.sts_client_mock.assume_role.assert_called_once_with(
459+
RoleArn="arn:aws:iam::123456789012:role/test-role",
460+
RoleSessionName="EKSDescribeClusterSession"
461+
)
462+
463+
# Verify that the EKS client was created with the assumed credentials
464+
self.mock_create_client.assert_any_call(
465+
"eks",
466+
aws_access_key_id="test-access-key",
467+
aws_secret_access_key="test-secret-key",
468+
aws_session_token="test-session-token"
469+
)
470+
471+
# Verify that the cluster was described
472+
self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")
473+
474+
# Assert the configuration state
475+
self.assert_config_state("valid_existing", "output_combined")
476+
477+
def test_no_assume_role(self):
478+
"""
479+
Test that assume_role_arn is not used when not provided.
480+
"""
481+
configs = ["valid_existing"]
482+
passed = "valid_existing"
483+
environment = []
484+
485+
self.client.describe_cluster = mock.Mock(return_value=describe_cluster_response())
486+
self.assert_cmd(configs, passed, environment)
487+
488+
# Verify that assume_role was not called
489+
self.mock_create_client.assert_called_once_with("eks")
490+
self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")

tests/functional/eks/test_util.py

+9
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,12 @@ def describe_cluster_deleting_response():
176176
"createdAt": 1500000000.000
177177
}
178178
}
179+
180+
def assume_role_response():
181+
return {
182+
"Credentials": {
183+
"AccessKeyId": "test-access-key",
184+
"SecretAccessKey": "test-secret-key",
185+
"SessionToken": "test-session-token"
186+
}
187+
}

0 commit comments

Comments
 (0)