Skip to content

Commit e722658

Browse files
authored
Remove expiry from STS token config and make it configurable via CLI (#2704)
1 parent dad4dec commit e722658

File tree

4 files changed

+98
-9
lines changed

4 files changed

+98
-9
lines changed

src/zenml/cli/service_connectors.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,65 @@ def prompt_expiration_time(
268268
return expiration_seconds
269269

270270

271+
def prompt_expires_at(
272+
default: Optional[datetime] = None,
273+
) -> Optional[datetime]:
274+
"""Prompt the user for an expiration timestamp.
275+
276+
Args:
277+
default: The default expiration time.
278+
279+
Returns:
280+
The expiration time provided by the user.
281+
"""
282+
if default is None:
283+
confirm = click.confirm(
284+
"Are the credentials you configured temporary? If so, you'll be asked "
285+
"to provide an expiration time in the next step.",
286+
default=False,
287+
)
288+
if not confirm:
289+
return None
290+
291+
while True:
292+
default_str = ""
293+
if default is not None:
294+
seconds = int((default - datetime.utcnow()).total_seconds())
295+
default_str = (
296+
f" [{str(default)} i.e. in "
297+
f"{seconds_to_human_readable(seconds)}]"
298+
)
299+
300+
expires_at = click.prompt(
301+
"Please enter the exact UTC date and time when the credentials "
302+
f"will expire e.g. '2023-12-31 23:59:59'{default_str}",
303+
type=click.DateTime(),
304+
default=default,
305+
show_default=False,
306+
)
307+
308+
assert expires_at is not None
309+
assert isinstance(expires_at, datetime)
310+
if expires_at < datetime.utcnow():
311+
cli_utils.warning(
312+
"The expiration time must be in the future. Please enter a "
313+
"later date and time."
314+
)
315+
continue
316+
317+
seconds = int((expires_at - datetime.utcnow()).total_seconds())
318+
319+
confirm = click.confirm(
320+
f"Credentials will be valid until {str(expires_at)} UTC (i.e. "
321+
f"in {seconds_to_human_readable(seconds)}. Keep this value?",
322+
default=True,
323+
)
324+
if confirm:
325+
break
326+
327+
return expires_at
328+
329+
271330
@service_connector.command(
272331
"register",
273332
context_settings={"ignore_unknown_options": True},
@@ -367,6 +426,16 @@ def prompt_expiration_time(
367426
required=False,
368427
type=str,
369428
)
429+
@click.option(
430+
"--expires-at",
431+
"expires_at",
432+
help="The exact UTC date and time when the credentials configured for this "
433+
"connector will expire. Takes the form 'YYYY-MM-DD HH:MM:SS'. This is only "
434+
"required if you are configuring a service connector with expiring "
435+
"credentials.",
436+
required=False,
437+
type=click.DateTime(),
438+
)
370439
@click.option(
371440
"--expires-skew-tolerance",
372441
"expires_skew_tolerance",
@@ -444,6 +513,7 @@ def register_service_connector(
444513
resource_type: Optional[str] = None,
445514
resource_id: Optional[str] = None,
446515
auth_method: Optional[str] = None,
516+
expires_at: Optional[datetime] = None,
447517
expires_skew_tolerance: Optional[int] = None,
448518
expiration_seconds: Optional[int] = None,
449519
no_verify: bool = False,
@@ -463,6 +533,8 @@ def register_service_connector(
463533
resource_type: The type of resource to connect to.
464534
resource_id: The ID of the resource to connect to.
465535
auth_method: The authentication method to use.
536+
expires_at: The exact UTC date and time when the credentials configured
537+
for this connector will expire.
466538
expires_skew_tolerance: The tolerance, in seconds, allowed when
467539
determining when the credentials configured for or generated by
468540
this connector will expire.
@@ -495,8 +567,6 @@ def register_service_connector(
495567
# Parse the given labels
496568
parsed_labels = cast(Dict[str, str], cli_utils.get_parsed_labels(labels))
497569

498-
expires_at: Optional[datetime] = None
499-
500570
if interactive:
501571
# Get the list of available service connector types
502572
connector_types = client.list_service_connector_types(
@@ -764,6 +834,9 @@ def register_service_connector(
764834
default=auth_method_spec.default_expiration_seconds,
765835
)
766836

837+
# Prompt for the time when the credentials will expire
838+
expires_at = prompt_expires_at(expires_at)
839+
767840
try:
768841
# Validate the connector configuration and fetch all available
769842
# resources that are accessible with the provided configuration
@@ -781,6 +854,7 @@ def register_service_connector(
781854
auth_method=auth_method,
782855
resource_type=resource_type,
783856
configuration=config_dict,
857+
expires_at=expires_at,
784858
expires_skew_tolerance=expires_skew_tolerance,
785859
expiration_seconds=expiration_seconds,
786860
auto_configure=False,
@@ -1176,6 +1250,14 @@ def describe_service_connector(
11761250
required=False,
11771251
type=str,
11781252
)
1253+
@click.option(
1254+
"--expires-at",
1255+
"expires_at",
1256+
help="The time at which the credentials configured for this connector "
1257+
"will expire.",
1258+
required=False,
1259+
type=click.DateTime(),
1260+
)
11791261
@click.option(
11801262
"--expires-skew-tolerance",
11811263
"expires_skew_tolerance",
@@ -1244,6 +1326,7 @@ def update_service_connector(
12441326
resource_type: Optional[str] = None,
12451327
resource_id: Optional[str] = None,
12461328
auth_method: Optional[str] = None,
1329+
expires_at: Optional[datetime] = None,
12471330
expires_skew_tolerance: Optional[int] = None,
12481331
expiration_seconds: Optional[int] = None,
12491332
no_verify: bool = False,
@@ -1264,6 +1347,8 @@ def update_service_connector(
12641347
resource_type: The type of resource to connect to.
12651348
resource_id: The ID of the resource to connect to.
12661349
auth_method: The authentication method to use.
1350+
expires_at: The time at which the credentials configured for this
1351+
connector will expire.
12671352
expires_skew_tolerance: The tolerance, in seconds, allowed when
12681353
determining when the credentials configured for or generated by
12691354
this connector will expire.
@@ -1425,6 +1510,9 @@ def update_service_connector(
14251510
or auth_method_spec.default_expiration_seconds,
14261511
)
14271512

1513+
# Prompt for the time when the credentials will expire
1514+
expires_at = prompt_expires_at(expires_at or connector.expires_at)
1515+
14281516
try:
14291517
# Validate the connector configuration and fetch all available
14301518
# resources that are accessible with the provided configuration
@@ -1442,6 +1530,7 @@ def update_service_connector(
14421530
# should be removed in the update if not set here
14431531
resource_type=resource_type or "",
14441532
configuration=config_dict,
1533+
expires_at=expires_at,
14451534
# Use zero value to indicate that the expiration time
14461535
# should be removed in the update if not set here
14471536
expiration_seconds=expiration_seconds or 0,
@@ -1534,6 +1623,7 @@ def update_service_connector(
15341623
# should be removed in the update if not set here
15351624
resource_id=resource_id or "",
15361625
description=description,
1626+
expires_at=expires_at,
15371627
# Use empty string to indicate that the expiration time
15381628
# should be removed in the update if not set here
15391629
expiration_seconds=expiration_seconds or 0,

src/zenml/cli/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1684,7 +1684,7 @@ def expires_in(
16841684
expires_at -= datetime.timedelta(seconds=skew_tolerance)
16851685
if expires_at < now:
16861686
return expired_str
1687-
return seconds_to_human_readable((expires_at - now).seconds)
1687+
return seconds_to_human_readable(int((expires_at - now).total_seconds()))
16881688

16891689

16901690
def print_service_connectors_table(

src/zenml/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4965,6 +4965,7 @@ def update_service_connector(
49654965
configuration: Optional[Dict[str, str]] = None,
49664966
resource_id: Optional[str] = None,
49674967
description: Optional[str] = None,
4968+
expires_at: Optional[datetime] = None,
49684969
expires_skew_tolerance: Optional[int] = None,
49694970
expiration_seconds: Optional[int] = None,
49704971
labels: Optional[Dict[str, Optional[str]]] = None,
@@ -5008,6 +5009,7 @@ def update_service_connector(
50085009
If set to the empty string, the existing resource ID will be
50095010
removed.
50105011
description: The description of the service connector.
5012+
expires_at: The new UTC expiration time of the service connector.
50115013
expires_skew_tolerance: The allowed expiration skew for the service
50125014
connector credentials.
50135015
expiration_seconds: The expiration time of the service connector.
@@ -5074,11 +5076,13 @@ def update_service_connector(
50745076
connector_type=connector.connector_type,
50755077
description=description or connector_model.description,
50765078
auth_method=auth_method or connector_model.auth_method,
5079+
expires_at=expires_at,
50775080
expires_skew_tolerance=expires_skew_tolerance,
50785081
expiration_seconds=expiration_seconds,
50795082
user=self.active_user.id,
50805083
workspace=self.active_workspace.id,
50815084
)
5085+
50825086
# Validate and configure the resources
50835087
if configuration is not None:
50845088
# The supplied configuration is a drop-in replacement for the

src/zenml/integrations/aws/service_connectors/aws_service_connector.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,6 @@ class AWSSecretKeyConfig(AWSBaseConfig, AWSSecretKey):
142142
class STSTokenConfig(AWSBaseConfig, STSToken):
143143
"""AWS STS token authentication configuration."""
144144

145-
expires_at: Optional[datetime.datetime] = Field(
146-
default=None,
147-
title="AWS STS Token Expiration",
148-
)
149-
150145

151146
class IAMRoleAuthenticationConfig(AWSSecretKeyConfig, AWSSessionPolicy):
152147
"""AWS IAM authentication config."""
@@ -983,7 +978,7 @@ def _authenticate(
983978
aws_session_token=cfg.aws_session_token.get_secret_value(),
984979
region_name=cfg.region,
985980
)
986-
return session, cfg.expires_at
981+
return session, self.expires_at
987982
elif auth_method in [
988983
AWSAuthenticationMethods.IAM_ROLE,
989984
AWSAuthenticationMethods.SESSION_TOKEN,

0 commit comments

Comments
 (0)