Skip to content

Commit 4701042

Browse files
twarnockMark Beacom
authored andcommitted
changes to support STS. added in new environment and tags to support KMS and IAM roles. (#70)
1 parent 376d703 commit 4701042

File tree

9 files changed

+134
-276
lines changed

9 files changed

+134
-276
lines changed

cloudendure/cloudendure.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def __init__(
6464
self.destination_account: str = self.config.active_config.get(
6565
"destination_account", ""
6666
)
67+
self.destination_kms: str = self.config.active_config.get("destination_kms", "")
68+
self.destination_role: str = self.config.active_config.get(
69+
"destination_role", ""
70+
)
6771
self.target_machines: List[str] = self.config.active_config.get(
6872
"machines", ""
6973
).split(",")
@@ -124,6 +128,16 @@ def get_project_id(self, project_name: str = "") -> str:
124128
return ""
125129
return project_id
126130

131+
def _get_role_credentials(self, name: str, role: str) -> Dict[str, Any]:
132+
_sts_client: boto_client = boto3.client("sts")
133+
134+
print(f"Assuming role: {role}")
135+
assumed_role: Dict[str, Any] = _sts_client.assume_role(
136+
RoleArn=self.destination_role, RoleSessionName=name
137+
)
138+
139+
return assumed_role.get("Credentials")
140+
127141
def check(self) -> bool:
128142
"""Check the status of machines in the provided project."""
129143
print(
@@ -291,6 +305,8 @@ def update_blueprint(self) -> bool:
291305
},
292306
{"key": "MigrationWave", "value": self.migration_wave},
293307
{"key": "DestinationAccount", "value": self.destination_account},
308+
{"key": "DestinationKMS", "value": self.destination_kms},
309+
{"key": "DestinationRole", "value": self.destination_role},
294310
]
295311

296312
blueprint["publicIPAction"] = self.config.active_config.get(
@@ -627,25 +643,33 @@ def create_ami(self) -> Dict[str, str]:
627643
return amis
628644
return amis
629645

630-
def copy_image(self, image_id: str, kms_id: str) -> str:
646+
def copy_image(self, image_id: str) -> str:
631647
"""Copy a shared image to an account.
632648
633649
Args:
634650
image_id (str): The AWS AMI to be copied.
635-
kms_id (str): The AWS KMS ID to be used for image encryption.
636651
637652
Returns:
638653
str: The copied AWS AMI ID.
639654
640655
"""
641-
_ec2_client: boto_client = boto3.client("ec2", AWS_REGION)
656+
credentials = self._get_role_credentials("CopyImage", self.destination_role)
657+
658+
_ec2_client: boto_client = boto3.client(
659+
"ec2",
660+
region_name=AWS_REGION,
661+
aws_access_key_id=credentials["AccessKeyId"],
662+
aws_secret_access_key=credentials["SecretAccessKey"],
663+
aws_session_token=credentials["SessionToken"],
664+
)
642665

666+
print(f"Copying image {image_id}")
643667
new_image: Dict[str, Any] = _ec2_client.copy_image(
644668
SourceImageId=image_id,
645669
SourceRegion=AWS_REGION,
646670
Name=f"copied-{image_id}",
647671
Encrypted=True,
648-
KmsKeyId=kms_id,
672+
KmsKeyId=self.destination_kms,
649673
)
650674

651675
return new_image.get("ImageId", "")
@@ -660,8 +684,18 @@ def split_image(self, image_id: str) -> Dict[str, Any]:
660684
dict: The mapping of AWS EBS block devices.
661685
662686
"""
663-
print("Loading EC2 resource for region: ", AWS_REGION)
664-
_ec2_res = boto3.resource("ec2", AWS_REGION)
687+
print(
688+
f"Loading EC2 resource for region: {AWS_REGION} using role: {self.destination_role}"
689+
)
690+
credentials = self._get_role_credentials("SplitImage", self.destination_role)
691+
692+
_ec2_res: boto_client = boto3.resource(
693+
"ec2",
694+
region_name=AWS_REGION,
695+
aws_access_key_id=credentials["AccessKeyId"],
696+
aws_secret_access_key=credentials["SecretAccessKey"],
697+
aws_session_token=credentials["SessionToken"],
698+
)
665699

666700
# Access the image that needs to be split
667701
image = _ec2_res.Image(image_id)
@@ -700,6 +734,9 @@ def split_image(self, image_id: str) -> Dict[str, Any]:
700734
Tags=[{"Key": f"Drive-{drive}", "Value": json.dumps(drives[drive])}],
701735
)
702736

737+
# remove the old image
738+
image.deregister()
739+
703740
return root_ami
704741

705742
def gen_terraform(
@@ -726,7 +763,15 @@ def gen_terraform(
726763
str: The raw Terraform with volume, ENI, and EC2 instance templates.
727764
728765
"""
729-
_ec2_res = boto3.resource("ec2", AWS_REGION)
766+
credentials = self._get_role_credentials("GenTerraform", self.destination_role)
767+
768+
_ec2_res: boto_client = boto3.resource(
769+
"ec2",
770+
region_name=AWS_REGION,
771+
aws_access_key_id=credentials["AccessKeyId"],
772+
aws_secret_access_key=credentials["SecretAccessKey"],
773+
aws_session_token=credentials["SessionToken"],
774+
)
730775

731776
# Access the image
732777
image: str = _ec2_res.Image(image_id)

cloudendure/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class CloudEndureConfig:
3333
"migration_wave": "0",
3434
"clone_status": "NOT_STARTED",
3535
"destination_account": "",
36+
"destination_kms": "",
37+
"destination_role": "",
3638
"disk_type": "SSD",
3739
"public_ip": "DONT_ALLOCATE",
3840
}

step/iam.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ data "aws_iam_policy_document" "lambda-invoke" {
2424
"*",
2525
]
2626
}
27+
# role(s) that the lambdas are allowed to assume roles on for copy, split, and tf generation
28+
statement {
29+
effect = "Allow"
30+
actions = [ "sts:AssumeRole" ]
31+
resources = [ "role/arn" ]
32+
}
2733
}
2834

2935
resource "aws_iam_policy" "lambda-invoke" {
@@ -45,7 +51,6 @@ resource "aws_iam_role" "iam_for_lambda" {
4551
data "aws_iam_policy_document" "iam_for_lambda_assume_role" {
4652
statement {
4753
actions = ["sts:AssumeRole"]
48-
4954
principals {
5055
type = "Service"
5156
identifiers = ["lambda.amazonaws.com"]

step/lambdas.tf

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ resource "aws_lambda_function" "lambda_split_image" {
8585
depends_on = ["data.archive_file.lambdas"]
8686
}
8787

88-
// resource "aws_lambda_function" "lambda_image_cleanup" {
89-
// filename = "lambdas.zip"
90-
// function_name = "tf-image-cleanup"
91-
// role = "${aws_iam_role.iam_for_lambda.arn}"
92-
// handler = "image_cleanup.lambda_handler"
93-
// source_code_hash = "${data.archive_file.lambdas.output_base64sha256}"
94-
// runtime = "python3.7"
95-
// depends_on = ["data.archive_file.lambdas"]
96-
// }
88+
resource "aws_lambda_function" "lambda_image_cleanup" {
89+
filename = "lambdas.zip"
90+
function_name = "tf-image-cleanup"
91+
role = "${aws_iam_role.iam_for_lambda.arn}"
92+
handler = "image_cleanup.lambda_handler"
93+
source_code_hash = "${data.archive_file.lambdas.output_base64sha256}"
94+
runtime = "python3.7"
95+
depends_on = ["data.archive_file.lambdas"]
96+
}

step/lambdas/copy_image.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,24 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> str:
2626
migrated_ami_id: str = event["migrated_ami_id"]
2727
kms_id: str = event["kms_id"]
2828
region: str = event.get("region", os.environ.get("AWS_REGION"))
29-
ec2_client = boto3.client("ec2", region)
29+
role: str = event.get("role")
30+
31+
sts_client = boto3.client("sts")
32+
33+
print(f"Assuming role: {role}")
34+
assumed_role: Dict[str, Any] = sts_client.assume_role(
35+
RoleArn=role, RoleSessionName="CopyImageLambda"
36+
)
37+
38+
credentials = assumed_role.get("Credentials")
39+
40+
ec2_client = boto3.client(
41+
"ec2",
42+
region_name=region,
43+
aws_access_key_id=credentials["AccessKeyId"],
44+
aws_secret_access_key=credentials["SecretAccessKey"],
45+
aws_session_token=credentials["SessionToken"],
46+
)
3047

3148
try:
3249
new_image: Dict[str, Any] = ec2_client.copy_image(

step/lambdas/find_instance.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> str:
5656
if tag["Key"] == "DestinationAccount":
5757
event_dict["account"] = tag["Value"]
5858

59-
if tag["Key"] == "KMSId":
59+
if tag["Key"] == "DestinationKMS":
6060
event_dict["kms_id"] = tag["Value"]
6161

62+
if tag["Key"] == "DestinationRole":
63+
event_dict["role"] = tag["Value"]
64+
6265
except Exception as e:
6366
print(e)
6467
event_dict["instance_id"] = "not-found"

step/lambdas/image_cleanup.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
from __future__ import annotations
55

66
import json
7-
from typing import Any, Dict
7+
import os
8+
from typing import Any, Dict, List
89

910
import boto3
1011

1112
print("Loading function image_cleanup")
1213

13-
ec2_client = boto3.client("ec2")
14-
1514
# {
1615
# "ami_id": "original AMI",
1716
# "kms_id": "KMS GUID used for copy and split AMI",
@@ -25,10 +24,24 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> bool:
2524
"""Handle signaling and entry into the AWS Lambda."""
2625
print("Received event: " + json.dumps(event, indent=2))
2726

28-
copy_ami: str = event.get("copy_ami")
29-
if not copy_ami:
30-
return ""
31-
32-
ec2_client.deregister_image(ImageId=copy_ami)
27+
migrated_ami: str = event.get("migrated_ami_id")
28+
if not migrated_ami:
29+
return False
30+
31+
ec2_res = boto3.resource("ec2", os.environ.get("AWS_REGION"))
32+
try:
33+
# Access the image that needs to be deleted
34+
image = ec2_res.Image(migrated_ami)
35+
36+
# grab device mappings before deregistering
37+
devices: List[Any] = image.block_device_mappings
38+
image.deregister()
39+
for device in devices:
40+
if "Ebs" in device:
41+
snap = ec2_res.Snapshot(device["Ebs"].get("SnapshotId"))
42+
snap.delete()
43+
except Exception as e:
44+
print(f"Failed. AMI may not exist.\n{str(e)}")
45+
return False
3346

3447
return True

step/lambdas/split_image.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,24 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> str:
2525

2626
copy_ami: str = event["copy_ami"]
2727
region: str = event.get("region", os.environ.get("AWS_REGION"))
28-
ec2_res = boto3.resource("ec2", region)
28+
role: str = event.get("role")
29+
30+
sts_client = boto3.client("sts")
31+
32+
print(f"Assuming role: {role}")
33+
assumed_role: Dict[str, Any] = sts_client.assume_role(
34+
RoleArn=role, RoleSessionName="SplitImageLambda"
35+
)
36+
37+
credentials = assumed_role.get("Credentials")
38+
39+
ec2_res = boto3.resource(
40+
"ec2",
41+
region_name=region,
42+
aws_access_key_id=credentials["AccessKeyId"],
43+
aws_secret_access_key=credentials["SecretAccessKey"],
44+
aws_session_token=credentials["SessionToken"],
45+
)
2946

3047
try:
3148
# Access the image that needs to be split

0 commit comments

Comments
 (0)