11
11
#
12
12
# Copyright (c) 2023 ScyllaDB
13
13
14
+ import datetime
14
15
import logging
15
16
16
17
import botocore
@@ -29,7 +30,7 @@ class AwsKms:
29
30
def __init__ (self , region_names ):
30
31
if not region_names :
31
32
raise ValueError ("'region_names' parameter cannot be empty" )
32
- self .region_names = region_names if isinstance (region_names , list ) else [region_names ]
33
+ self .region_names = region_names if isinstance (region_names , ( list , tuple ) ) else [region_names ]
33
34
self .mapping = {
34
35
region_name : {
35
36
'client' : boto3 .client ('kms' , region_name = region_name ),
@@ -70,7 +71,7 @@ def get_kms_keys(self, region_name, next_marker=None):
70
71
if kms_keys .get ("NextMarker" ):
71
72
yield from self .get_kms_keys (region_name = region_name , next_marker = kms_keys ["NextMarker" ])
72
73
73
- def find_or_create_suitable_kms_keys (self ):
74
+ def find_or_create_suitable_kms_keys (self , only_find = False ):
74
75
for region_name in self .region_names :
75
76
if self .NUM_OF_KMS_KEYS <= len (self .mapping [region_name ]['kms_key_ids' ]):
76
77
continue
@@ -87,6 +88,8 @@ def find_or_create_suitable_kms_keys(self):
87
88
self .mapping [region_name ]['kms_key_ids' ].append (current_kms_key_id )
88
89
if self .NUM_OF_KMS_KEYS == len (self .mapping [region_name ]['kms_key_ids' ]):
89
90
break
91
+ if only_find :
92
+ continue
90
93
while self .NUM_OF_KMS_KEYS > len (self .mapping [region_name ]['kms_key_ids' ]):
91
94
self .create_kms_key (region_name )
92
95
@@ -152,3 +155,46 @@ def delete_alias(self, kms_key_alias_name, tolerate_errors=True):
152
155
LOGGER .debug (exc .response )
153
156
if not tolerate_errors :
154
157
raise
158
+
159
+ def cleanup_old_aliases (self , time_delta_h : int = 48 , tolerate_errors : bool = True , dry_run = False ):
160
+ # NOTE: since the KMS alias creation date depends on the time zone of each specific region
161
+ # which may easily differ from the timezone of the caller we assume that deviation may be up to 24h.
162
+ # So, if it is needed to make sure that some test must have an alias for 24h then
163
+ # it is guaranteed only having margin to be '24h' -> 24 + 24 = 48h.
164
+ LOGGER .info ("KMS: Search for aliases older than '%d' hours" , time_delta_h )
165
+ alias_allowed_date = datetime .datetime .now (datetime .timezone .utc ) - datetime .timedelta (hours = 25 )
166
+ dry_run_prefix = "[dry-run]" if dry_run else ""
167
+ for region_name in self .region_names :
168
+ try :
169
+ if not self .mapping [region_name ].get ("kms_key_ids" ):
170
+ self .find_or_create_suitable_kms_keys (only_find = True )
171
+ kms_keys = self .mapping [region_name ].get ("kms_key_ids" , [])
172
+ current_client = self .mapping [region_name ]['client' ]
173
+ for kms_key_id in kms_keys :
174
+ LOGGER .info ("KMS: %s[region '%s'][key '%s'] read aliases" , dry_run_prefix , region_name , kms_key_id )
175
+ current_aliases = current_client .list_aliases (KeyId = kms_key_id , Limit = 999 )["Aliases" ]
176
+ # {'AliasName': 'alias/qa-kms-key-for-rotation-1',
177
+ # 'CreationDate': datetime.datetime(2023, 8, 11, 18, 33, 12, 45000, tzinfo=tzlocal()), ... }
178
+ for current_alias in current_aliases :
179
+ current_alias_name = current_alias .get ("AliasName" , "notfound" )
180
+ current_alias_creation_date = current_alias .get ("CreationDate" )
181
+ if not current_alias_name .startswith ("alias/testid-" ):
182
+ LOGGER .info (
183
+ "KMS: %s[region '%s'][key '%s'] ignore the '%s' alias as not matching" ,
184
+ dry_run_prefix , region_name , kms_key_id , current_alias_name )
185
+ continue
186
+ if current_alias_creation_date < alias_allowed_date :
187
+ LOGGER .info (
188
+ "KMS: %s[region '%s'][key '%s'] %s old alias -> '%s' (%s)" ,
189
+ dry_run_prefix , region_name , kms_key_id ,
190
+ ("found" if dry_run else "deleting" ),
191
+ current_alias_name , current_alias_creation_date )
192
+ if not dry_run :
193
+ self .delete_alias (current_alias_name , tolerate_errors = tolerate_errors )
194
+ except botocore .exceptions .ClientError as exc :
195
+ LOGGER .info (
196
+ "KMS: failed to process old aliases in the '%s' region: %s" ,
197
+ region_name , exc .response )
198
+ if not tolerate_errors :
199
+ raise
200
+ LOGGER .info ("KMS: finished cleaning up old aliases" )
0 commit comments