kubernetes-watch 0.1.5__py3-none-any.whl → 0.1.9__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.
- kube_watch/enums/kube.py +5 -5
- kube_watch/enums/logic.py +8 -8
- kube_watch/enums/providers.py +12 -12
- kube_watch/enums/workflow.py +17 -17
- kube_watch/models/common.py +16 -16
- kube_watch/models/workflow.py +60 -60
- kube_watch/modules/clusters/kube.py +185 -185
- kube_watch/modules/database/__init__.py +0 -0
- kube_watch/modules/database/model.py +12 -0
- kube_watch/modules/database/postgre.py +271 -0
- kube_watch/modules/logic/actions.py +55 -55
- kube_watch/modules/logic/checks.py +7 -7
- kube_watch/modules/logic/load.py +23 -23
- kube_watch/modules/logic/merge.py +31 -31
- kube_watch/modules/logic/scheduler.py +74 -74
- kube_watch/modules/mock/mock_generator.py +53 -53
- kube_watch/modules/providers/aws.py +210 -210
- kube_watch/modules/providers/git.py +120 -32
- kube_watch/modules/providers/github.py +126 -126
- kube_watch/modules/providers/vault.py +188 -188
- kube_watch/standalone/metarecogen/ckan_to_gn.py +132 -132
- kube_watch/watch/__init__.py +1 -1
- kube_watch/watch/helpers.py +170 -170
- kube_watch/watch/workflow.py +232 -100
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/LICENSE +21 -21
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/METADATA +5 -3
- kubernetes_watch-0.1.9.dist-info/RECORD +36 -0
- kubernetes_watch-0.1.5.dist-info/RECORD +0 -33
- {kubernetes_watch-0.1.5.dist-info → kubernetes_watch-0.1.9.dist-info}/WHEEL +0 -0
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import random
|
|
3
|
-
from prefect import runtime
|
|
4
|
-
|
|
5
|
-
def generate_number():
|
|
6
|
-
return 42
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def mock_dict_data():
|
|
10
|
-
return {
|
|
11
|
-
"key1": "value1",
|
|
12
|
-
"key2": "value2",
|
|
13
|
-
"key3": {
|
|
14
|
-
"k1": 1, "k2": 2, "k3": [1, 3, "ali"]
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
def print_input(params):
|
|
19
|
-
print(params)
|
|
20
|
-
|
|
21
|
-
def print_number(number, dummy_param, env_var_name):
|
|
22
|
-
print(f"The generated number is: {number} and the dummy_value is: {dummy_param}")
|
|
23
|
-
return number, dummy_param, env_var_name
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def print_flow_parameters():
|
|
27
|
-
assert runtime.flow_run.parameters.get("WORK_DIR") is not None
|
|
28
|
-
assert runtime.flow_run.parameters.get("MODULE_PATH") is not None
|
|
29
|
-
print(runtime.flow_run.parameters.get("WORK_DIR"))
|
|
30
|
-
print(runtime.flow_run.parameters.get("MODULE_PATH"))
|
|
31
|
-
|
|
32
|
-
def print_from_flow_parameters(work_dir):
|
|
33
|
-
"""
|
|
34
|
-
work_dir is provided via flow parameters
|
|
35
|
-
"""
|
|
36
|
-
assert work_dir != ''
|
|
37
|
-
print(work_dir)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def delay(seconds):
|
|
41
|
-
time.sleep(seconds)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def random_boolean():
|
|
45
|
-
return random.choice([True, False])
|
|
46
|
-
|
|
47
|
-
def merge_bools(inp_dict):
|
|
48
|
-
list_bools = [v for k,v in inp_dict.items()]
|
|
49
|
-
return any(list_bools)
|
|
50
|
-
|
|
51
|
-
def print_result(task_name, result):
|
|
52
|
-
print(f'=========== {task_name} RESULT =================')
|
|
53
|
-
print(result)
|
|
1
|
+
import time
|
|
2
|
+
import random
|
|
3
|
+
from prefect import runtime
|
|
4
|
+
|
|
5
|
+
def generate_number():
|
|
6
|
+
return 42
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def mock_dict_data():
|
|
10
|
+
return {
|
|
11
|
+
"key1": "value1",
|
|
12
|
+
"key2": "value2",
|
|
13
|
+
"key3": {
|
|
14
|
+
"k1": 1, "k2": 2, "k3": [1, 3, "ali"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def print_input(params):
|
|
19
|
+
print(params)
|
|
20
|
+
|
|
21
|
+
def print_number(number, dummy_param, env_var_name):
|
|
22
|
+
print(f"The generated number is: {number} and the dummy_value is: {dummy_param}")
|
|
23
|
+
return number, dummy_param, env_var_name
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_flow_parameters():
|
|
27
|
+
assert runtime.flow_run.parameters.get("WORK_DIR") is not None
|
|
28
|
+
assert runtime.flow_run.parameters.get("MODULE_PATH") is not None
|
|
29
|
+
print(runtime.flow_run.parameters.get("WORK_DIR"))
|
|
30
|
+
print(runtime.flow_run.parameters.get("MODULE_PATH"))
|
|
31
|
+
|
|
32
|
+
def print_from_flow_parameters(work_dir):
|
|
33
|
+
"""
|
|
34
|
+
work_dir is provided via flow parameters
|
|
35
|
+
"""
|
|
36
|
+
assert work_dir != ''
|
|
37
|
+
print(work_dir)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def delay(seconds):
|
|
41
|
+
time.sleep(seconds)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def random_boolean():
|
|
45
|
+
return random.choice([True, False])
|
|
46
|
+
|
|
47
|
+
def merge_bools(inp_dict):
|
|
48
|
+
list_bools = [v for k,v in inp_dict.items()]
|
|
49
|
+
return any(list_bools)
|
|
50
|
+
|
|
51
|
+
def print_result(task_name, result):
|
|
52
|
+
print(f'=========== {task_name} RESULT =================')
|
|
53
|
+
print(result)
|
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
#========================================================================
|
|
2
|
-
# This class is deprecated. Please refer to aws.py
|
|
3
|
-
#========================================================================
|
|
4
|
-
import json
|
|
5
|
-
import base64
|
|
6
|
-
from datetime import datetime , timezone, timedelta
|
|
7
|
-
import boto3
|
|
8
|
-
from botocore.exceptions import ClientError
|
|
9
|
-
from prefect import get_run_logger
|
|
10
|
-
from kube_watch.enums.providers import AwsResources
|
|
11
|
-
|
|
12
|
-
logger = get_run_logger()
|
|
13
|
-
|
|
14
|
-
def create_session(aws_creds):
|
|
15
|
-
"""Create a boto3 session."""
|
|
16
|
-
session = boto3.Session(
|
|
17
|
-
aws_access_key_id=aws_creds.get('data').get('access_key'),
|
|
18
|
-
aws_secret_access_key=aws_creds.get('data').get('secret_key'),
|
|
19
|
-
aws_session_token=aws_creds.get('data').get('security_token'), # Using .get() for optional fields
|
|
20
|
-
)
|
|
21
|
-
logger.info("Created AWS Session Successfully!")
|
|
22
|
-
return session
|
|
23
|
-
|
|
24
|
-
#========================================================================================
|
|
25
|
-
# ECR
|
|
26
|
-
#========================================================================================
|
|
27
|
-
def _prepare_ecr_secret_data(username, password, auth_token, ecr_url):
|
|
28
|
-
key_url = f"https://{ecr_url}"
|
|
29
|
-
docker_config_dict = {
|
|
30
|
-
"auths": {
|
|
31
|
-
key_url: {
|
|
32
|
-
"username": username,
|
|
33
|
-
"password": password,
|
|
34
|
-
"auth": auth_token,
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return {'.dockerconfigjson': json.dumps(docker_config_dict)}
|
|
39
|
-
|
|
40
|
-
def task_get_access_token(session, resource, region, base_image_url):
|
|
41
|
-
|
|
42
|
-
if resource == AwsResources.ECR:
|
|
43
|
-
ecr_client = session.client('ecr', region_name=region)
|
|
44
|
-
token_response = ecr_client.get_authorization_token()
|
|
45
|
-
decoded_token = base64.b64decode(token_response['authorizationData'][0]['authorizationToken']).decode()
|
|
46
|
-
username, password = decoded_token.split(':')
|
|
47
|
-
return _prepare_ecr_secret_data(username, password, token_response['authorizationData'][0]['authorizationToken'], base_image_url)
|
|
48
|
-
# return {'username': username, 'password': password}
|
|
49
|
-
|
|
50
|
-
raise ValueError('Unknown resource')
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def task_get_latest_image_digest(session, resource, region, repository_name, tag):
|
|
54
|
-
"""
|
|
55
|
-
Fetches the digest of the latest image from the specified ECR repository.
|
|
56
|
-
"""
|
|
57
|
-
if resource == AwsResources.ECR:
|
|
58
|
-
ecr_client = session.client('ecr', region_name=region)
|
|
59
|
-
try:
|
|
60
|
-
response = ecr_client.describe_images(
|
|
61
|
-
repositoryName=repository_name,
|
|
62
|
-
imageIds = [{'imageTag': tag}],
|
|
63
|
-
filter={'tagStatus': 'TAGGED'}
|
|
64
|
-
)
|
|
65
|
-
images = response['imageDetails']
|
|
66
|
-
# Assuming 'latest' tag is used correctly, there should be only one such image
|
|
67
|
-
if images:
|
|
68
|
-
# Extracting the digest of the latest image
|
|
69
|
-
return images[0]['imageDigest']
|
|
70
|
-
except Exception as e:
|
|
71
|
-
logger.error(f"Error fetching latest image digest: {e}")
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
raise ValueError('Unknown resource')
|
|
75
|
-
|
|
76
|
-
#========================================================================================
|
|
77
|
-
# IAM Cred update
|
|
78
|
-
#========================================================================================
|
|
79
|
-
def task_rotate_iam_creds(
|
|
80
|
-
session,
|
|
81
|
-
user_name,
|
|
82
|
-
old_access_key_id,
|
|
83
|
-
old_access_key_secret,
|
|
84
|
-
access_key_id_var_name,
|
|
85
|
-
access_secret_key_var_name,
|
|
86
|
-
rotate_interval,
|
|
87
|
-
require_smtp_conversion = False,
|
|
88
|
-
ses_region = "ap-southeast-2"
|
|
89
|
-
):
|
|
90
|
-
iam = session.client('iam')
|
|
91
|
-
creation_date = None
|
|
92
|
-
|
|
93
|
-
# Retrieve the specified access key
|
|
94
|
-
has_key_exist = False
|
|
95
|
-
try:
|
|
96
|
-
response = iam.list_access_keys(UserName=user_name)
|
|
97
|
-
for key in response['AccessKeyMetadata']:
|
|
98
|
-
if key['AccessKeyId'] == old_access_key_id:
|
|
99
|
-
creation_date = key['CreateDate']
|
|
100
|
-
has_key_exist = True
|
|
101
|
-
break
|
|
102
|
-
except ClientError as error:
|
|
103
|
-
logger.error(f"Error retrieving key: {error}")
|
|
104
|
-
raise Exception(f"Error retrieving key: {error}")
|
|
105
|
-
|
|
106
|
-
if not has_key_exist:
|
|
107
|
-
logger.error(f"The provided Access Key ID; {old_access_key_id} does not exist.")
|
|
108
|
-
raise KeyError(f"The provided Access Key ID; {old_access_key_id} does not exist.")
|
|
109
|
-
|
|
110
|
-
dd, hh, mm = list(map(lambda x: int(x), rotate_interval.split(":")))
|
|
111
|
-
|
|
112
|
-
curr_date = datetime.now(timezone.utc)
|
|
113
|
-
# Check if the key needs rotation
|
|
114
|
-
if (curr_date - creation_date > timedelta(days=dd,hours=hh,minutes=mm)):
|
|
115
|
-
logger.info("Key is older than rotation period, rotating now.")
|
|
116
|
-
# Delete the old key
|
|
117
|
-
delete_iam_user_key(session, user_name, old_access_key_id)
|
|
118
|
-
|
|
119
|
-
# Create a new access key
|
|
120
|
-
access_key_id, secret_access_key = create_iam_user_key(session, user_name)
|
|
121
|
-
if require_smtp_conversion:
|
|
122
|
-
secret_access_key = convert_to_smtp_password(secret_access_key, ses_region)
|
|
123
|
-
return {access_key_id_var_name: access_key_id, access_secret_key_var_name: secret_access_key}
|
|
124
|
-
|
|
125
|
-
else:
|
|
126
|
-
logger.info("Key rotation not necessary.")
|
|
127
|
-
return {access_key_id_var_name: old_access_key_id, access_secret_key_var_name: old_access_key_secret}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def create_iam_user_key(session, user_name):
|
|
132
|
-
iam = session.client('iam')
|
|
133
|
-
# Check if the user exists
|
|
134
|
-
try:
|
|
135
|
-
iam.get_user(UserName=user_name)
|
|
136
|
-
logger.info("User exists, proceeding to create access key.")
|
|
137
|
-
except iam.exceptions.NoSuchEntityException:
|
|
138
|
-
raise Exception(f"User '{user_name}' does not exist, cannot proceed with key creation.")
|
|
139
|
-
except ClientError as error:
|
|
140
|
-
error_code = error.response['Error']['Code']
|
|
141
|
-
if error_code == 'NoSuchEntity':
|
|
142
|
-
raise Exception(f"User '{user_name}' does not exist in AWS IAM.")
|
|
143
|
-
else:
|
|
144
|
-
raise Exception(f"An unexpected error occurred: {error.response['Error']['Message']}")
|
|
145
|
-
|
|
146
|
-
# Create access key for this user
|
|
147
|
-
response = iam.create_access_key(UserName=user_name)
|
|
148
|
-
access_key_id = response['AccessKey']['AccessKeyId']
|
|
149
|
-
secret_access_key = response['AccessKey']['SecretAccessKey']
|
|
150
|
-
|
|
151
|
-
# print(access_key_id)
|
|
152
|
-
# print(secret_access_key)
|
|
153
|
-
|
|
154
|
-
return access_key_id, secret_access_key
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def delete_iam_user_key(session, user_name, access_key_id):
|
|
158
|
-
iam = session.client('iam')
|
|
159
|
-
try:
|
|
160
|
-
iam.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)
|
|
161
|
-
logger.info(f"Old key {access_key_id} deleted successfully.")
|
|
162
|
-
except Exception as e:
|
|
163
|
-
raise Exception(f"Failed to delete old key: {e}")
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def convert_to_smtp_password(secret_access_key, region):
|
|
168
|
-
"""Convert IAM Secret Key to SMTP Password."""
|
|
169
|
-
import hmac
|
|
170
|
-
import hashlib
|
|
171
|
-
import base64
|
|
172
|
-
|
|
173
|
-
SMTP_REGIONS = [
|
|
174
|
-
'us-east-2', # US East (Ohio)
|
|
175
|
-
'us-east-1', # US East (N. Virginia)
|
|
176
|
-
'us-west-2', # US West (Oregon)
|
|
177
|
-
'ap-south-1', # Asia Pacific (Mumbai)
|
|
178
|
-
'ap-northeast-2', # Asia Pacific (Seoul)
|
|
179
|
-
'ap-southeast-1', # Asia Pacific (Singapore)
|
|
180
|
-
'ap-southeast-2', # Asia Pacific (Sydney)
|
|
181
|
-
'ap-northeast-1', # Asia Pacific (Tokyo)
|
|
182
|
-
'ca-central-1', # Canada (Central)
|
|
183
|
-
'eu-central-1', # Europe (Frankfurt)
|
|
184
|
-
'eu-west-1', # Europe (Ireland)
|
|
185
|
-
'eu-west-2', # Europe (London)
|
|
186
|
-
'sa-east-1', # South America (Sao Paulo)
|
|
187
|
-
'us-gov-west-1', # AWS GovCloud (US)
|
|
188
|
-
]
|
|
189
|
-
|
|
190
|
-
# These values are required to calculate the signature. Do not change them.
|
|
191
|
-
DATE = "11111111"
|
|
192
|
-
SERVICE = "ses"
|
|
193
|
-
MESSAGE = "SendRawEmail"
|
|
194
|
-
TERMINAL = "aws4_request"
|
|
195
|
-
VERSION = 0x04
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def sign(key, msg):
|
|
199
|
-
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
|
|
200
|
-
|
|
201
|
-
if region not in SMTP_REGIONS:
|
|
202
|
-
raise ValueError(f"The {region} Region doesn't have an SMTP endpoint.")
|
|
203
|
-
|
|
204
|
-
signature = sign(("AWS4" + secret_access_key).encode('utf-8'), DATE)
|
|
205
|
-
signature = sign(signature, region)
|
|
206
|
-
signature = sign(signature, SERVICE)
|
|
207
|
-
signature = sign(signature, TERMINAL)
|
|
208
|
-
signature = sign(signature, MESSAGE)
|
|
209
|
-
signature_and_version = bytes([VERSION]) + signature
|
|
210
|
-
smtp_password = base64.b64encode(signature_and_version)
|
|
1
|
+
#========================================================================
|
|
2
|
+
# This class is deprecated. Please refer to aws.py
|
|
3
|
+
#========================================================================
|
|
4
|
+
import json
|
|
5
|
+
import base64
|
|
6
|
+
from datetime import datetime , timezone, timedelta
|
|
7
|
+
import boto3
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
from prefect import get_run_logger
|
|
10
|
+
from kube_watch.enums.providers import AwsResources
|
|
11
|
+
|
|
12
|
+
logger = get_run_logger()
|
|
13
|
+
|
|
14
|
+
def create_session(aws_creds):
|
|
15
|
+
"""Create a boto3 session."""
|
|
16
|
+
session = boto3.Session(
|
|
17
|
+
aws_access_key_id=aws_creds.get('data').get('access_key'),
|
|
18
|
+
aws_secret_access_key=aws_creds.get('data').get('secret_key'),
|
|
19
|
+
aws_session_token=aws_creds.get('data').get('security_token'), # Using .get() for optional fields
|
|
20
|
+
)
|
|
21
|
+
logger.info("Created AWS Session Successfully!")
|
|
22
|
+
return session
|
|
23
|
+
|
|
24
|
+
#========================================================================================
|
|
25
|
+
# ECR
|
|
26
|
+
#========================================================================================
|
|
27
|
+
def _prepare_ecr_secret_data(username, password, auth_token, ecr_url):
|
|
28
|
+
key_url = f"https://{ecr_url}"
|
|
29
|
+
docker_config_dict = {
|
|
30
|
+
"auths": {
|
|
31
|
+
key_url: {
|
|
32
|
+
"username": username,
|
|
33
|
+
"password": password,
|
|
34
|
+
"auth": auth_token,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {'.dockerconfigjson': json.dumps(docker_config_dict)}
|
|
39
|
+
|
|
40
|
+
def task_get_access_token(session, resource, region, base_image_url):
|
|
41
|
+
|
|
42
|
+
if resource == AwsResources.ECR:
|
|
43
|
+
ecr_client = session.client('ecr', region_name=region)
|
|
44
|
+
token_response = ecr_client.get_authorization_token()
|
|
45
|
+
decoded_token = base64.b64decode(token_response['authorizationData'][0]['authorizationToken']).decode()
|
|
46
|
+
username, password = decoded_token.split(':')
|
|
47
|
+
return _prepare_ecr_secret_data(username, password, token_response['authorizationData'][0]['authorizationToken'], base_image_url)
|
|
48
|
+
# return {'username': username, 'password': password}
|
|
49
|
+
|
|
50
|
+
raise ValueError('Unknown resource')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def task_get_latest_image_digest(session, resource, region, repository_name, tag):
|
|
54
|
+
"""
|
|
55
|
+
Fetches the digest of the latest image from the specified ECR repository.
|
|
56
|
+
"""
|
|
57
|
+
if resource == AwsResources.ECR:
|
|
58
|
+
ecr_client = session.client('ecr', region_name=region)
|
|
59
|
+
try:
|
|
60
|
+
response = ecr_client.describe_images(
|
|
61
|
+
repositoryName=repository_name,
|
|
62
|
+
imageIds = [{'imageTag': tag}],
|
|
63
|
+
filter={'tagStatus': 'TAGGED'}
|
|
64
|
+
)
|
|
65
|
+
images = response['imageDetails']
|
|
66
|
+
# Assuming 'latest' tag is used correctly, there should be only one such image
|
|
67
|
+
if images:
|
|
68
|
+
# Extracting the digest of the latest image
|
|
69
|
+
return images[0]['imageDigest']
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Error fetching latest image digest: {e}")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
raise ValueError('Unknown resource')
|
|
75
|
+
|
|
76
|
+
#========================================================================================
|
|
77
|
+
# IAM Cred update
|
|
78
|
+
#========================================================================================
|
|
79
|
+
def task_rotate_iam_creds(
|
|
80
|
+
session,
|
|
81
|
+
user_name,
|
|
82
|
+
old_access_key_id,
|
|
83
|
+
old_access_key_secret,
|
|
84
|
+
access_key_id_var_name,
|
|
85
|
+
access_secret_key_var_name,
|
|
86
|
+
rotate_interval,
|
|
87
|
+
require_smtp_conversion = False,
|
|
88
|
+
ses_region = "ap-southeast-2"
|
|
89
|
+
):
|
|
90
|
+
iam = session.client('iam')
|
|
91
|
+
creation_date = None
|
|
92
|
+
|
|
93
|
+
# Retrieve the specified access key
|
|
94
|
+
has_key_exist = False
|
|
95
|
+
try:
|
|
96
|
+
response = iam.list_access_keys(UserName=user_name)
|
|
97
|
+
for key in response['AccessKeyMetadata']:
|
|
98
|
+
if key['AccessKeyId'] == old_access_key_id:
|
|
99
|
+
creation_date = key['CreateDate']
|
|
100
|
+
has_key_exist = True
|
|
101
|
+
break
|
|
102
|
+
except ClientError as error:
|
|
103
|
+
logger.error(f"Error retrieving key: {error}")
|
|
104
|
+
raise Exception(f"Error retrieving key: {error}")
|
|
105
|
+
|
|
106
|
+
if not has_key_exist:
|
|
107
|
+
logger.error(f"The provided Access Key ID; {old_access_key_id} does not exist.")
|
|
108
|
+
raise KeyError(f"The provided Access Key ID; {old_access_key_id} does not exist.")
|
|
109
|
+
|
|
110
|
+
dd, hh, mm = list(map(lambda x: int(x), rotate_interval.split(":")))
|
|
111
|
+
|
|
112
|
+
curr_date = datetime.now(timezone.utc)
|
|
113
|
+
# Check if the key needs rotation
|
|
114
|
+
if (curr_date - creation_date > timedelta(days=dd,hours=hh,minutes=mm)):
|
|
115
|
+
logger.info("Key is older than rotation period, rotating now.")
|
|
116
|
+
# Delete the old key
|
|
117
|
+
delete_iam_user_key(session, user_name, old_access_key_id)
|
|
118
|
+
|
|
119
|
+
# Create a new access key
|
|
120
|
+
access_key_id, secret_access_key = create_iam_user_key(session, user_name)
|
|
121
|
+
if require_smtp_conversion:
|
|
122
|
+
secret_access_key = convert_to_smtp_password(secret_access_key, ses_region)
|
|
123
|
+
return {access_key_id_var_name: access_key_id, access_secret_key_var_name: secret_access_key}
|
|
124
|
+
|
|
125
|
+
else:
|
|
126
|
+
logger.info("Key rotation not necessary.")
|
|
127
|
+
return {access_key_id_var_name: old_access_key_id, access_secret_key_var_name: old_access_key_secret}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def create_iam_user_key(session, user_name):
|
|
132
|
+
iam = session.client('iam')
|
|
133
|
+
# Check if the user exists
|
|
134
|
+
try:
|
|
135
|
+
iam.get_user(UserName=user_name)
|
|
136
|
+
logger.info("User exists, proceeding to create access key.")
|
|
137
|
+
except iam.exceptions.NoSuchEntityException:
|
|
138
|
+
raise Exception(f"User '{user_name}' does not exist, cannot proceed with key creation.")
|
|
139
|
+
except ClientError as error:
|
|
140
|
+
error_code = error.response['Error']['Code']
|
|
141
|
+
if error_code == 'NoSuchEntity':
|
|
142
|
+
raise Exception(f"User '{user_name}' does not exist in AWS IAM.")
|
|
143
|
+
else:
|
|
144
|
+
raise Exception(f"An unexpected error occurred: {error.response['Error']['Message']}")
|
|
145
|
+
|
|
146
|
+
# Create access key for this user
|
|
147
|
+
response = iam.create_access_key(UserName=user_name)
|
|
148
|
+
access_key_id = response['AccessKey']['AccessKeyId']
|
|
149
|
+
secret_access_key = response['AccessKey']['SecretAccessKey']
|
|
150
|
+
|
|
151
|
+
# print(access_key_id)
|
|
152
|
+
# print(secret_access_key)
|
|
153
|
+
|
|
154
|
+
return access_key_id, secret_access_key
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def delete_iam_user_key(session, user_name, access_key_id):
|
|
158
|
+
iam = session.client('iam')
|
|
159
|
+
try:
|
|
160
|
+
iam.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)
|
|
161
|
+
logger.info(f"Old key {access_key_id} deleted successfully.")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
raise Exception(f"Failed to delete old key: {e}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def convert_to_smtp_password(secret_access_key, region):
|
|
168
|
+
"""Convert IAM Secret Key to SMTP Password."""
|
|
169
|
+
import hmac
|
|
170
|
+
import hashlib
|
|
171
|
+
import base64
|
|
172
|
+
|
|
173
|
+
SMTP_REGIONS = [
|
|
174
|
+
'us-east-2', # US East (Ohio)
|
|
175
|
+
'us-east-1', # US East (N. Virginia)
|
|
176
|
+
'us-west-2', # US West (Oregon)
|
|
177
|
+
'ap-south-1', # Asia Pacific (Mumbai)
|
|
178
|
+
'ap-northeast-2', # Asia Pacific (Seoul)
|
|
179
|
+
'ap-southeast-1', # Asia Pacific (Singapore)
|
|
180
|
+
'ap-southeast-2', # Asia Pacific (Sydney)
|
|
181
|
+
'ap-northeast-1', # Asia Pacific (Tokyo)
|
|
182
|
+
'ca-central-1', # Canada (Central)
|
|
183
|
+
'eu-central-1', # Europe (Frankfurt)
|
|
184
|
+
'eu-west-1', # Europe (Ireland)
|
|
185
|
+
'eu-west-2', # Europe (London)
|
|
186
|
+
'sa-east-1', # South America (Sao Paulo)
|
|
187
|
+
'us-gov-west-1', # AWS GovCloud (US)
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
# These values are required to calculate the signature. Do not change them.
|
|
191
|
+
DATE = "11111111"
|
|
192
|
+
SERVICE = "ses"
|
|
193
|
+
MESSAGE = "SendRawEmail"
|
|
194
|
+
TERMINAL = "aws4_request"
|
|
195
|
+
VERSION = 0x04
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def sign(key, msg):
|
|
199
|
+
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
|
|
200
|
+
|
|
201
|
+
if region not in SMTP_REGIONS:
|
|
202
|
+
raise ValueError(f"The {region} Region doesn't have an SMTP endpoint.")
|
|
203
|
+
|
|
204
|
+
signature = sign(("AWS4" + secret_access_key).encode('utf-8'), DATE)
|
|
205
|
+
signature = sign(signature, region)
|
|
206
|
+
signature = sign(signature, SERVICE)
|
|
207
|
+
signature = sign(signature, TERMINAL)
|
|
208
|
+
signature = sign(signature, MESSAGE)
|
|
209
|
+
signature_and_version = bytes([VERSION]) + signature
|
|
210
|
+
smtp_password = base64.b64encode(signature_and_version)
|
|
211
211
|
return smtp_password.decode('utf-8')
|