Skip to content

Commit 71f07ad

Browse files
authored
Merge pull request #6476 from micahhausler/eks-token-api-version-deprecation
Deprecate Kubernetes client API version v1alpha1
2 parents e564532 + 320f898 commit 71f07ad

28 files changed

Lines changed: 367 additions & 81 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "eks get-token",
4+
"description": "Add support for respecting API version found in KUBERNETES_EXEC_INFO environment variable"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "eks update-kubeconfig",
4+
"description": "Update default API version to v1beta1"
5+
}

awscli/customizations/eks/get_token.py

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import base64
1414
import botocore
1515
import json
16+
import os
17+
import sys
1618

1719
from datetime import datetime, timedelta
1820
from botocore.signers import RequestSigner
@@ -26,6 +28,33 @@
2628
AUTH_API_VERSION = "2011-06-15"
2729
AUTH_SIGNING_VERSION = "v4"
2830

31+
ALPHA_API = "client.authentication.k8s.io/v1alpha1"
32+
BETA_API = "client.authentication.k8s.io/v1beta1"
33+
V1_API = "client.authentication.k8s.io/v1"
34+
35+
FULLY_SUPPORTED_API_VERSIONS = [
36+
V1_API,
37+
BETA_API,
38+
]
39+
DEPRECATED_API_VERSIONS = [
40+
ALPHA_API,
41+
]
42+
43+
ERROR_MSG_TPL = (
44+
"{0} KUBERNETES_EXEC_INFO, defaulting to {1}. This is likely a "
45+
"bug in your Kubernetes client. Please update your Kubernetes "
46+
"client."
47+
)
48+
UNRECOGNIZED_MSG_TPL = (
49+
"Unrecognized API version in KUBERNETES_EXEC_INFO, defaulting to "
50+
"{0}. This is likely due to an outdated AWS "
51+
"CLI. Please update your AWS CLI."
52+
)
53+
DEPRECATION_MSG_TPL = (
54+
"Kubeconfig user entry is using deprecated API version {0}. Run "
55+
"'aws eks update-kubeconfig' to update."
56+
)
57+
2958
# Presigned url timeout in seconds
3059
URL_TIMEOUT = 60
3160

@@ -39,32 +68,40 @@
3968
class GetTokenCommand(BasicCommand):
4069
NAME = 'get-token'
4170

42-
DESCRIPTION = ("Get a token for authentication with an Amazon EKS cluster. "
43-
"This can be used as an alternative to the "
44-
"aws-iam-authenticator.")
71+
DESCRIPTION = (
72+
"Get a token for authentication with an Amazon EKS cluster. "
73+
"This can be used as an alternative to the "
74+
"aws-iam-authenticator."
75+
)
4576

4677
ARG_TABLE = [
4778
{
4879
'name': 'cluster-name',
49-
'help_text': ("Specify the name of the Amazon EKS cluster to create a token for."),
50-
'required': True
80+
'help_text': (
81+
"Specify the name of the Amazon EKS cluster to create a token for."
82+
),
83+
'required': True,
5184
},
5285
{
5386
'name': 'role-arn',
54-
'help_text': ("Assume this role for credentials when signing the token."),
55-
'required': False
56-
}
87+
'help_text': (
88+
"Assume this role for credentials when signing the token."
89+
),
90+
'required': False,
91+
},
5792
]
5893

5994
def get_expiration_time(self):
60-
token_expiration = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRATION_MINS)
95+
token_expiration = datetime.utcnow() + timedelta(
96+
minutes=TOKEN_EXPIRATION_MINS
97+
)
6198
return token_expiration.strftime('%Y-%m-%dT%H:%M:%SZ')
6299

63100
def _run_main(self, parsed_args, parsed_globals):
64101
client_factory = STSClientFactory(self._session)
65102
sts_client = client_factory.get_sts_client(
66-
region_name=parsed_globals.region,
67-
role_arn=parsed_args.role_arn)
103+
region_name=parsed_globals.region, role_arn=parsed_args.role_arn
104+
)
68105
token = TokenGenerator(sts_client).get_token(parsed_args.cluster_name)
69106

70107
# By default STS signs the url for 15 minutes so we are creating a
@@ -74,28 +111,94 @@ def _run_main(self, parsed_args, parsed_globals):
74111

75112
full_object = {
76113
"kind": "ExecCredential",
77-
"apiVersion": "client.authentication.k8s.io/v1alpha1",
114+
"apiVersion": self.discover_api_version(),
78115
"spec": {},
79116
"status": {
80117
"expirationTimestamp": token_expiration,
81-
"token": token
82-
}
118+
"token": token,
119+
},
83120
}
84121

85122
uni_print(json.dumps(full_object))
86123
uni_print('\n')
87124
return 0
88125

126+
def discover_api_version(self):
127+
"""
128+
Parses the KUBERNETES_EXEC_INFO environment variable and returns the
129+
API version. If the environment variable is empty, malformed, or
130+
invalid, return the v1alpha1 response and print a message to stderr.
131+
132+
If the v1alpha1 API is specified explicitly, a message is printed to
133+
stderr with instructions to update.
134+
135+
:return: The client authentication API version
136+
:rtype: string
137+
"""
138+
# At the time Kubernetes v1.29 is released upstream (approx Dec 2023),
139+
# "v1beta1" will be removed. At or around that time, EKS will likely
140+
# support v1.22 through v1.28, in which client API version "v1beta1"
141+
# will be supported by all EKS versions.
142+
fallback_api_version = ALPHA_API
143+
144+
error_prefixes = {
145+
"error": "Error parsing",
146+
"empty": "Empty",
147+
}
148+
149+
exec_info_raw = os.environ.get("KUBERNETES_EXEC_INFO", "")
150+
if not exec_info_raw:
151+
# All kube clients should be setting this. Otherewise, we'll return
152+
# the fallback and write an error.
153+
uni_print(
154+
ERROR_MSG_TPL.format(
155+
error_prefixes["empty"],
156+
fallback_api_version,
157+
),
158+
sys.stderr,
159+
)
160+
uni_print("\n", sys.stderr)
161+
return fallback_api_version
162+
try:
163+
exec_info = json.loads(exec_info_raw)
164+
except json.JSONDecodeError:
165+
# The environment variable was malformed
166+
uni_print(
167+
ERROR_MSG_TPL.format(
168+
error_prefixes["error"],
169+
fallback_api_version,
170+
),
171+
sys.stderr,
172+
)
173+
uni_print("\n", sys.stderr)
174+
return fallback_api_version
175+
176+
api_version_raw = exec_info.get("apiVersion")
177+
if api_version_raw in FULLY_SUPPORTED_API_VERSIONS:
178+
return api_version_raw
179+
elif api_version_raw in DEPRECATED_API_VERSIONS:
180+
uni_print(DEPRECATION_MSG_TPL.format(ALPHA_API), sys.stderr)
181+
uni_print("\n", sys.stderr)
182+
return api_version_raw
183+
else:
184+
uni_print(
185+
UNRECOGNIZED_MSG_TPL.format(fallback_api_version),
186+
sys.stderr,
187+
)
188+
uni_print("\n", sys.stderr)
189+
return fallback_api_version
190+
89191

90192
class TokenGenerator(object):
91193
def __init__(self, sts_client):
92194
self._sts_client = sts_client
93195

94196
def get_token(self, cluster_name):
95-
""" Generate a presigned url token to pass to kubectl. """
197+
"""Generate a presigned url token to pass to kubectl."""
96198
url = self._get_presigned_url(cluster_name)
97199
token = TOKEN_PREFIX + base64.urlsafe_b64encode(
98-
url.encode('utf-8')).decode('utf-8').rstrip('=')
200+
url.encode('utf-8')
201+
).decode('utf-8').rstrip('=')
99202
return token
100203

101204
def _get_presigned_url(self, cluster_name):
@@ -112,9 +215,7 @@ def __init__(self, session):
112215
self._session = session
113216

114217
def get_sts_client(self, region_name=None, role_arn=None):
115-
client_kwargs = {
116-
'region_name': region_name
117-
}
218+
client_kwargs = {'region_name': region_name}
118219
if role_arn is not None:
119220
creds = self._get_role_credentials(region_name, role_arn)
120221
client_kwargs['aws_access_key_id'] = creds['AccessKeyId']
@@ -127,18 +228,17 @@ def get_sts_client(self, region_name=None, role_arn=None):
127228
def _get_role_credentials(self, region_name, role_arn):
128229
sts = self._session.create_client('sts', region_name)
129230
return sts.assume_role(
130-
RoleArn=role_arn,
131-
RoleSessionName='EKSGetTokenAuth'
231+
RoleArn=role_arn, RoleSessionName='EKSGetTokenAuth'
132232
)['Credentials']
133233

134234
def _register_cluster_name_handlers(self, sts_client):
135235
sts_client.meta.events.register(
136236
'provide-client-params.sts.GetCallerIdentity',
137-
self._retrieve_cluster_name
237+
self._retrieve_cluster_name,
138238
)
139239
sts_client.meta.events.register(
140240
'before-sign.sts.GetCallerIdentity',
141-
self._inject_cluster_name_header
241+
self._inject_cluster_name_header,
142242
)
143243

144244
def _retrieve_cluster_name(self, params, context, **kwargs):
@@ -147,5 +247,6 @@ def _retrieve_cluster_name(self, params, context, **kwargs):
147247

148248
def _inject_cluster_name_header(self, request, **kwargs):
149249
if 'eks_cluster' in request.context:
150-
request.headers[
151-
CLUSTER_NAME_HEADER] = request.context['eks_cluster']
250+
request.headers[CLUSTER_NAME_HEADER] = request.context[
251+
'eks_cluster'
252+
]

awscli/customizations/eks/update_kubeconfig.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131

3232
DEFAULT_PATH = os.path.expanduser("~/.kube/config")
3333

34-
# Use the endpoint for kubernetes 1.10
35-
# To get the most recent endpoint we will need to
36-
# Do a check on the cluster's version number
37-
API_VERSION = "client.authentication.k8s.io/v1alpha1"
34+
# At the time EKS no longer supports Kubernetes v1.21 (probably ~Dec 2023),
35+
# this can be safely changed to default to writing "v1"
36+
API_VERSION = "client.authentication.k8s.io/v1beta1"
3837

3938
class UpdateKubeconfigCommand(BasicCommand):
4039
NAME = 'update-kubeconfig'
@@ -306,7 +305,7 @@ def get_user_entry(self):
306305
"--cluster-name",
307306
self._cluster_name,
308307
]),
309-
("command", "aws")
308+
("command", "aws"),
310309
]))
311310
]))
312311
])

awscli/examples/eks/get-token.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Output::
1010

1111
{
1212
"kind": "ExecCredential",
13-
"apiVersion": "client.authentication.k8s.io/v1alpha1",
13+
"apiVersion": "client.authentication.k8s.io/v1beta1",
1414
"spec": {},
1515
"status": {
1616
"expirationTimestamp": "2019-08-14T18:44:27Z",

0 commit comments

Comments
 (0)