We are going to perform the following steps:
The hub AWS Account is the source of truth for our stacks. Spoke AWS accounts are consumers of these stacks, you can think of them as accounts that need governance controls applied. For this workshop, we are using the same account as both the hub and spoke for simplicity; in a multi-account setup, these could be separate AWS Accounts and Regions.
Here are the steps you need to follow to “Creating the Lambda”
Navigate to the ServiceCatalogFactory CodeCommit repository
Scroll down to the bottom of the page and hit the Create file button
Copy the following snippet into the main input field:
Schema: factory-2019-04-01
Stacks:
- Name: "delete-default-networking-function"
Versions:
- Name: "v1"
Active: True
Source:
Provider: "CodeCommit"
Configuration:
RepositoryName: "delete-default-networking-function"
BranchName: "main"
Set the File name to stacks/network-workshop.yaml
Set your Author name
Set your Email address
Set your Commit message
Using a good / unique commit message will help you understand what is going on later.
The YAML file we created in the CodeCommit repository told the framework to:
Once you have made your changes the ServiceCatalogFactory Pipeline should have run. If you were very quick in making the change, the pipeline may still be running. If it has not yet started feel free to the hit the Release change button.
Once it has completed it should show the Source and Build stages in green to indicate they have completed successfully:
The screenshots may differ slightly as the design of AWS CodePipeline changes. You should see a pipeline where each stage is green.
When you configured your product version, you specified the following version:
Versions:
- Name: "v1"
Active: True
Source:
Provider: "CodeCommit"
Configuration:
RepositoryName: "delete-default-networking-function"
BranchName: "main"
This tells the framework the source code for the product comes from the main branch of a CodeCommit repository of the name delete-default-networking-function.
We now need to create the CodeCommit repository and add the AWS CloudFormation template we are going to use for our product.
Navigate to AWS CodeCommit
Click Create repository
Input the repository name delete-default-networking-function
Click Create
AWSTemplateFormatVersion: "2010-09-09"
Description: |
Deletes the following default networking components from AWS Accounts:
1) Deletes the internet gateway
2) Deletes the subnets
4) Deletes the network access lists
5) Deletes the security groups
6) Deletes the default VPC
{"framework": "servicecatalog-products", "role": "product", "product-set": "delete-default-vpc", "product": "delete-default-vpc", "version": "v1"}
Parameters:
DeleteDefaultNetworkingRoleNameToAssume:
Description: "Name of the IAM Role that will be assumed in the spoke account to remove networking"
Type: String
Default: "servicecatalog-puppet/PuppetRole"
DeleteDefaultVPCLambdaExecutionIAMRoleName:
Description: "Name of the IAM Role that will be created to execute the lambda function"
Type: String
Default: "DeleteDefaultVPCLambdaExecution"
DeleteDefaultVPCLambdaExecutionIAMRolePath:
Description: "The path for the IAM Role that will be created to execute the lambda function"
Type: String
Default: "/DeleteDefaultVPCLambdaExecution/"
DeleteDefaultNetworkingLambdaFunctionName:
Description: "The name to give the function that deletes the default networking resources"
Type: String
Default: "DeleteDefaultNetworking"
Resources:
DefaultVpcDeletionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref DeleteDefaultVPCLambdaExecutionIAMRoleName
Path: !Ref DeleteDefaultVPCLambdaExecutionIAMRolePath
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: Organizations
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "sts:AssumeRole"
Resource: !Sub 'arn:${AWS::Partition}:iam::*:role/${DeleteDefaultNetworkingRoleNameToAssume}'
- Effect: Allow
Action:
- "ec2:DescribeInternetGateways"
- "ec2:DetachInternetGateway"
- "ec2:DeleteInternetGateway"
- "ec2:DescribeSubnets"
- "ec2:DeleteSubnet"
- "ec2:DescribeRouteTables"
- "ec2:DeleteRouteTable"
- "ec2:DescribeNetworkAcls"
- "ec2:DeleteNetworkAcl"
- "ec2:DeleteSecurityGroup"
- "ec2:DeleteVpc"
- "ec2:DescribeRegions"
- "ec2:DescribeAccountAttributes"
- "ec2:DescribeNetworkInterfaces"
- "ec2:DescribeSecurityGroups"
- "ec2:DeleteSecurityGroup"
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: '*'
Function:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref DeleteDefaultNetworkingLambdaFunctionName
Handler: index.lambda_handler
Runtime: python3.7
Timeout: 600
Role: !GetAtt DefaultVpcDeletionRole.Arn
Environment:
Variables:
DeleteDefaultNetworkingRoleNameToAssume: !Ref DeleteDefaultNetworkingRoleNameToAssume
Partition: !Sub '${AWS::Partition}'
Code:
ZipFile: |
import boto3, logging, traceback, os
from boto3 import Session
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logging.basicConfig(
format='%(levelname)s %(threadName)s [%(filename)s:%(lineno)d] %(message)s',
datefmt='%Y-%m-%d:%H:%M:%S',
level=logging.INFO
)
def delete_igw(client, vpc_id):
fltr = [{'Name': 'attachment.vpc-id', 'Values': [vpc_id]}]
try:
igw = client.describe_internet_gateways(Filters=fltr)['InternetGateways']
if igw:
igw_id = igw[0]['InternetGatewayId']
client.detach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id)
client.delete_internet_gateway(InternetGatewayId=igw_id)
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def delete_subnets(client):
try:
subs = client.describe_subnets()['Subnets']
if subs:
for sub in subs:
sub_id = sub['SubnetId']
client.delete_subnet(SubnetId=sub_id)
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def delete_rtbs(client):
try:
rtbs = client.describe_route_tables()['RouteTables']
if rtbs:
for rtb in rtbs:
main = False
for assoc in rtb['Associations']:
main = assoc['Main']
if main:
continue
rtb_id = rtb['RouteTableId']
client.delete_route_table(RouteTableId=rtb_id)
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def delete_acls(client):
try:
acls = client.describe_network_acls()['NetworkAcls']
if acls:
for acl in acls:
default = acl['IsDefault']
if default:
continue
acl_id = acl['NetworkAclId']
client.delete_network_acl(NetworkAclId=acl_id)
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def delete_sgps(client):
try:
sgps = client.describe_security_groups()['SecurityGroups']
if sgps:
for sgp in sgps:
default = sgp['GroupName']
if default == 'default':
continue
sg_id = sgp['GroupId']
client.delete_security_group(GroupId=sg_id)
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def delete_vpc(client, vpc_id, region):
try:
client.delete_vpc(VpcId=vpc_id)
logger.info('VPC {} has been deleted from the {} region.'.format(vpc_id, region))
return
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
raise
def lambda_handler(e, c):
account_id=e.get("account_id")
region=e.get("region")
role_arn = f"arn:{os.getenv('Partition')}:iam::{account_id}:role/{os.getenv('DeleteDefaultNetworkingRoleNameToAssume')}"
sts = Session().client('sts')
assumed_role_object = sts.assume_role(
RoleArn=role_arn,
RoleSessionName="spoke",
)
credentials = assumed_role_object['Credentials']
kwargs = {
"service_name": "ec2",
"aws_access_key_id": credentials['AccessKeyId'],
"aws_secret_access_key": credentials['SecretAccessKey'],
"aws_session_token": credentials['SessionToken'],
}
ec2 = Session().client(**kwargs, region_name=region)
try:
attribs = ec2.describe_account_attributes(AttributeNames=['default-vpc'])['AccountAttributes']
vpc_id = attribs[0]['AttributeValues'][0]['AttributeValue']
if vpc_id == 'none':
logger.info('Default VPC not found in {}'.format(region))
return
# Since most resources are attached an ENI, this checks for additional resources
f = [{'Name': 'vpc-id', 'Values': [vpc_id]}]
eni = ec2.describe_network_interfaces(Filters=f)['NetworkInterfaces']
if eni:
logger.error('VPC {} has existing resources in the {} region.'.format(vpc_id, region))
return
delete_igw(ec2, vpc_id)
delete_subnets(ec2)
delete_rtbs(ec2)
delete_acls(ec2)
delete_sgps(ec2)
delete_vpc(ec2, vpc_id, region)
except Exception as ex:
logger.error(ex)
traceback.print_tb(ex.__traceback__)
Set the File name to stack.template.yaml
Set your Author name
Set your Email address
Set your Commit message
Using a good / unique commit message will help you understand what is going on later.
Creating that file should trigger your stack–delete-default-networking-function-v1-pipeline.
Once the pipeline has completed it should show the stages in green to indicate they have completed successfully:
You should see your commit message on this screen, it will help you know which version of ServiceCatalogFactory repository the pipeline is processing.
The screenshots may differ slightly as the design of AWS CodePipeline changes. You should see a pipeline where each stage is green.
You have now successfully created a stack!
Now that you have verified the pipeline has run correctly you can go to Amazon S3 to view the stack.
Navigate to https://s3.console.aws.amazon.com/s3/home
Select the bucket named sc-puppet-stacks-repository-<account_id>
Navigate to stack/delete-default-networking-function/v1 where you should see an object named stack.template.yaml