Skip to content

feat: add ami start and record lambda #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions components/base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: InstallBase
description: This document installs all required packages on top of Amazon Linux 2
schemaVersion: 1.0

phases:
- name: build
steps:
- name: InstallBase
action: ExecuteBash
inputs:
commands:
- sudo rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH
- sudo echo -e "[wazuh]\ngpgcheck=1\ngpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH\nenabled=1\nname=EL-\$releasever - Wazuh\nbaseurl=https://packages.wazuh.com/4.x/yum/\nprotect=1" >> /etc/yum.repos.d/wazuh.repo
- WAZUH_MANAGER="10.0.0.2" sudo yum install -y wazuh-agent
- sudo systemctl daemon-reload
- sudo systemctl enable wazuh-agent
- sudo systemctl start wazuh-agent
- sudo sed -i "s/^enabled=1/enabled=0/" /etc/yum.repos.d/wazuh.repo
- sudo amazon-linux-extras install epel
- sudo yum update -y
- sudo yum install -y redis
- sudo yum install -y postgresql14
- sudo yum install -y clamav freshclam clamav-update

4 changes: 2 additions & 2 deletions deploy_imagebuilder.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ echo "AWS CDK is installed successfully."

# Check for AWS CDK
if ! command -v cdk &> /dev/null; then
echo "AWS CDK could not be found. Please rerun 'bash install.sh' with Sudo access and ensure the command is available within the \$PATH"
echo "AWS CDK could not be found. Please rerun 'bash deploy_imagebuilder.sh' with Sudo access and ensure the command is available within the \$PATH"
exit 1
fi

Expand All @@ -120,7 +120,7 @@ esac

# Check if AWS CLI installation was successful
if ! command -v aws &> /dev/null; then
echo "AWS CLI could not be found. Please rerun 'bash install.sh' with Sudo access and ensure the command is available within the $PATH"
echo "AWS CLI could not be found. Please rerun 'bash deploy_imagebuilder.sh' with Sudo access and ensure the command is available within the $PATH"
exit 1
fi

Expand Down
23 changes: 22 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,26 @@ show_install_options() {
echo "${bold}${green}2. Production Ready ${reset} - ${bold}${blue}Optimized for scalability and performance, leveraging the power of AWS EKS for robust, enterprise-grade deployments.${reset}"
}

declare base_ami,envoy_ami,squid_ami

check_image_builder() {
ssm=$(aws ssm get-parameters \
--names base_image_ami squid_image_ami envoy_image_ami \
--query "Parameters[*].{Name:Name,Value:Value}" --output json)

length=$(echo "$ssm" | jq -r ".|length")

if [ "$length" -lt 3 ]; then
display_error "Unable to find base images for Proxy Servers. Please run the following command: bash deploy_imagebuilder.sh\nIf you have done it already please wait for 15-20 mins until it builds the images"
exit 1
fi

base_ami=$(echo "$ssm" | jq '.[]|select(.Name=="base_image_ami")|.Value')
envoy_ami=$(echo "$ssm" | jq '.[]|select(.Name=="envoy_image_ami")|.Value')
squid_ami=$(echo "$ssm" | jq '.[]|select(.Name=="squid_image_ami")|.Value')

}

# Function to read user input until a valid choice is made
get_user_choice() {
while true; do
Expand All @@ -247,6 +267,7 @@ get_user_choice() {
clear
list_services
echo
check_image_builder
show_install_options
get_user_choice

Expand Down Expand Up @@ -481,7 +502,7 @@ if [[ "$INSTALLATION_MODE" == 2 ]]; then
aws iam delete-role --role-name $ROLE_NAME 2>/dev/null
cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION -c aws_arn=$AWS_ARN
fi
if cdk deploy --require-approval never -c db_pass=$DB_PASS -c admin_api_key=$ADMIN_API_KEY -c aws_arn=$AWS_ARN -c master_enc_key=$MASTER_ENC_KEY -c vpn_ips=$VPN_IPS $LOCKER; then
if cdk deploy --require-approval never -c db_pass=$DB_PASS -c admin_api_key=$ADMIN_API_KEY -c aws_arn=$AWS_ARN -c master_enc_key=$MASTER_ENC_KEY -c vpn_ips=$VPN_IPS -c base_ami=$base_ami -c envoy_ami=$envoy_ami -c squid_ami=$squid_ami $LOCKER; then
# Wait for the EKS Cluster to be deployed
echo $(aws eks create-addon --cluster-name hs-eks-cluster --addon-name amazon-cloudwatch-observability)
aws eks update-kubeconfig --region "$AWS_DEFAULT_REGION" --name hs-eks-cluster
Expand Down
170 changes: 166 additions & 4 deletions lib/aws/image_builder_stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import * as cdk from "aws-cdk-lib";
import * as image_builder from "aws-cdk-lib/aws-imagebuilder"
import * as iam from "aws-cdk-lib/aws-iam";

import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda";
import { LambdaSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
import { Vpc } from './networking';
import { Topic } from 'aws-cdk-lib/aws-sns';
import { Construct } from "constructs";
import { ImageBuilderConfig, VpcConfig } from "./config";
import { MachineImage } from 'aws-cdk-lib/aws-ec2';
import { aws_logs as logs } from 'aws-cdk-lib';
import { MachineImage, SubnetType, SecurityGroup } from 'aws-cdk-lib/aws-ec2';

import { readFileSync } from "fs";

Expand All @@ -19,13 +23,25 @@ type ImageBuilderProperties = {
baseimageArn: string;
description: string;
compFilePath: string;
snsTopicArn: string;
subnetId: string;
sgId: string;

}

type RecordLambdaProperties = {
name: string;
id: string;
role: iam.Role;
ssm_key: string;
topic: Topic,
}

function CreateImagePipeline(
stack: ImageBuilderStack,
role: iam.Role,
props: ImageBuilderProperties,
) {
): string {
const component = new image_builder.CfnComponent(stack, props.comp_id, {
name: props.comp_name,
description: props.description,
Expand All @@ -48,6 +64,9 @@ function CreateImagePipeline(
name: props.infra_config_name,
instanceTypes: ["t3.medium"],
instanceProfileName: props.profile_name,
snsTopicArn: props.snsTopicArn,
subnetId: props.subnetId,
securityGroupIds: [props.sgId]
})
squid_infra_config.addDependency(instance_profile)

Expand All @@ -58,6 +77,8 @@ function CreateImagePipeline(
})

pipeline.addDependency(squid_infra_config)
return pipeline.attrArn

}

export class ImageBuilderStack extends cdk.Stack {
Expand All @@ -71,7 +92,19 @@ export class ImageBuilderStack extends cdk.Stack {
maxAzs: 2,
};

const envoy_channel = new Topic(this, 'ImgBuilderNotificationTopicEnvoy', {});
const squid_channel = new Topic(this, 'ImgBuilderNotificationTopicSquid', {});
const base_channel = new Topic(this, 'ImgBuilderNotificationTopicBase', {});

let vpc = new Vpc(this, vpcConfig);

let subnetId = vpc.vpc.selectSubnets({ subnetType: SubnetType.PUBLIC }).subnetIds[0];
let ib_SG = new SecurityGroup(this, 'image-server-sg', {
vpc: vpc.vpc,
allowAllOutbound: true,
description: 'security group for a image builder server',
});

let role = new iam.Role(this, "StationRole", { roleName: "StationRole", assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com") })

role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"))
Expand All @@ -89,14 +122,18 @@ export class ImageBuilderStack extends cdk.Stack {
baseimageArn: base_image_id,
description: "Image builder for squid",
compFilePath: "./components/squid.yml",
sgId: ib_SG.securityGroupId,
subnetId: subnetId,
snsTopicArn: squid_channel.topicArn,
};

CreateImagePipeline(
let envoy_arn = CreateImagePipeline(
this,
role,
squid_properties,
)


let envoy_properties = {
pipeline_name: "EnvoyImagePipeline",
recipe_name: "EnvoyImageRecipe",
Expand All @@ -107,12 +144,137 @@ export class ImageBuilderStack extends cdk.Stack {
baseimageArn: base_image_id,
description: "Image builder for Envoy",
compFilePath: "./components/envoy.yml",
sgId: ib_SG.securityGroupId,
subnetId: subnetId,
snsTopicArn: envoy_channel.topicArn,
};

CreateImagePipeline(
let squid_arn = CreateImagePipeline(
this,
role,
envoy_properties,
)


let base_properties = {
pipeline_name: "BaseImagePipeline",
recipe_name: "BaseImageRecipe",
profile_name: "BaseStationProfile",
comp_name: "HyperswitchBaseImageBuilder",
comp_id: "install_base_component",
infra_config_name: "BaseInfraConfig",
baseimageArn: base_image_id,
description: "Image builder for Base Image",
compFilePath: "./components/base.yml",
sgId: ib_SG.securityGroupId,
subnetId: subnetId,
snsTopicArn: base_channel.topicArn,
};

let base_arn = CreateImagePipeline(
this,
role,
base_properties,
)

const start_ib_code = readFileSync(
"lib/aws/lambda/start_ib.py",
).toString();


const lambda_policy = new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['imagebuilder:StartImagePipelineExecution'],
resources: [
`arn:aws:imagebuilder:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account
}:image/*`,
`arn:aws:imagebuilder:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account
}:image-pipeline/*`,
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['ssm:*'],
resources: ["*"],
}),
],
});

const lambda_role = new iam.Role(this, "hyperswitch-ib-lambda-role", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
inlinePolicies: {
"ib-start-role": lambda_policy,
},
});

const ib_lambda = new Function(this, "hyperswitch-ib-lambda", {
functionName: "HyperswitchIbStartLambda",
runtime: Runtime.PYTHON_3_9,
handler: "index.lambda_handler",
code: Code.fromInline(start_ib_code),
timeout: cdk.Duration.minutes(15),
role: lambda_role,
environment: {
envoy_image_pipeline_arn: envoy_arn,
squid_image_pipeline_arn: squid_arn,
base_image_pipeline_arn: base_arn,
},
});


const triggerIbStart = new cdk.CustomResource(
this,
"HyperswitchIbStart",
{
serviceToken: ib_lambda.functionArn,
},
);

addRecordLambda(this, {
id: "hyperswitch-record-amid-squid",
name: "HyperswitchRecordAmiIdSquid",
role: lambda_role,
topic: squid_channel,
ssm_key: "squid_image_ami",
})

addRecordLambda(this, {
id: "hyperswitch-record-amid-envoy",
name: "HyperswitchRecordAmiIdEnvoy",
role: lambda_role,
topic: envoy_channel,
ssm_key: "envoy_image_ami",
})

addRecordLambda(this, {
id: "hyperswitch-record-amid-base",
name: "HyperswitchRecordAmiIdBase",
role: lambda_role,
topic: base_channel,
ssm_key: "base_image_ami",
})
}
}


function addRecordLambda(stack: cdk.Stack, props: RecordLambdaProperties) {
const record_amid_code = readFileSync(
"lib/aws/lambda/record_ami.py"
).toString();

let ib_record_function = new Function(stack, props.id, {
functionName: props.name,
runtime: Runtime.PYTHON_3_9,
handler: "index.lambda_handler",
code: Code.fromInline(record_amid_code),
timeout: cdk.Duration.minutes(15),
role: props.role,
environment: {
IMAGE_SSM_NAME: props.ssm_key,
},
});

props.topic.addSubscription(new LambdaSubscription(ib_record_function))
}
39 changes: 39 additions & 0 deletions lib/aws/lambda/record_ami.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from typing import Union
import json
import os
import logging
import boto3
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger(__name__)


def lambda_handler(event, _):
logger.info(event)

message_body = event.get("Records")[0].get("Sns").get("Message")
json_body = json.loads(message_body)
ami = json_body["outputResources"]["amis"][0]["image"]
ssm_key = os.environ["IMAGE_SSM_NAME"]
logger.info(f"updating ssm {ssm_key}")
ssm_client = boto3.client("ssm")
try:
result = ssm_client.put_parameter(
Name=ssm_key,
Value=ami,
Type="String",
DataType="text",
Tier="Advanced",
Overwrite=True,
)
logger.info(result)
except Exception as e:
logger.error(e)

return create_response(200, {})


def create_response(code: int, body: Union[dict, str]):
json_content = {
"statusCode": code,
}
return json_content
Loading