Full Packet Capture (ipynb)#

  • Steps below includes the implementation of necessary infrastructure (e.g. network, subnet, load balancer, compute instance(s), packet mirroring policy) at the analyst project to receive the captured packets.

  • Also includes setting up the target resource(s) (e.g. tagging, firewall rules) where packets would be captured from

  • Network traffic would be mirrored from target resource(s) to implemented infrastructure

  • Actual packet capture to pcap file still requires SSH into receiving compute instance and performing tcpdump

References

Install Dependencies#

Install the dependencies ipywidgets and pandas. Skip the next cell if they had already been installed.

!pip3 install ipywidgets pandas

Imports and Configuration#

import ipywidgets as widgets
import json
import os
import pandas as pd

from IPython.display import HTML, display

# extend width of widgets
display(HTML('''<style>
    .widget-label { min-width: 28ex !important; font-weight:bold; }
</style>'''))

# extend width and max rows of pandas output
pd.set_option('display.max_colwidth', None)
pd.set_option('display.max_rows', None)
# [OPTIONAL] authenticate using your service account
!gcloud auth activate-service-account --key-file <json_key_file>

Define Environment Variables#

Specify the following information

  • Source Project - Project id of target project (that contains potentially compromised compute instance)

# create text boxes for user input
src_project = widgets.Text(description = "Source Project: ", disabled=False, layout=widgets.Layout(width='40%'))
display(src_project)
# store user input in environment variables for use in subsequent comamnds
os.environ['SRC_PROJECT'] = src_project.value
# get zone, network, ip of VMs of interest in target (src) project
!gcloud compute instances list \
    --project $SRC_PROJECT \
    --format="table( \
    name, \
    zone.basename(), \
    networkInterfaces[].network.basename(), \
    networkInterfaces[].subnetwork.basename(), \
    networkInterfaces[].networkIP, \
    status)"

Specify the following information

Fields

Description

Source (Target)

Source Region

Region of the compute instance to capture packets from (e.g. asia-southeast1)

Source Zone

Zone of the compute instance to capture packets from (e.g. asia-southeast1-b)

Source Network

Name of network that contains the compute instance to capture packets from

Source IP Range

IP address range of network that contains the compute instance to capture packets from

Source VM

Name of compute instance to capture packets from

Destination (Analyst)

Destination Project

Project id of project where the compute instance to receive the packets would be created

Destination Network

Name of network where the compute instance to receive the packets would be created

Destination Subnet

Name of subnet where the compute instance to receive the packets would be created

Destination Subnet Range

Subnet range of above subnet

Destination Instance Group

Name of instance group where the compute instance to receive the packets would be created

Destination VM

Name of the created compute instance that would receive the packets

Destination SSH IP

IP address that would be used to SSH into the created compute instance that would receive the packets

Network

Mirror Network Tag

Name of tag to place on the compute instance to capture packets from

VPC Peering Name

Name of VPC network peering connection between the target network and the analyst network

LB Health Check Name

Name of health check (for load balancer which receives mirrored traffic)

LB Backend Name

Name of backend (for load balancer which receives mirrored traffic)

LB Frontend Name

Name of frontend (for load balancer which receives mirrored traffic)

Packet Mirroring Policy Name

Name of packet mirroring policy

# create text boxes for user input
print("====== SOURCE (TARGET)======")
src_region = widgets.Text(description = "Source Region: ", disabled=False, layout=widgets.Layout(width='40%'))
src_zone = widgets.Text(description = "Source Zone: ", disabled=False, layout=widgets.Layout(width='40%'))
src_network = widgets.Text(value='default', description = "Source Network: ", disabled=False, layout=widgets.Layout(width='40%'))
src_ip_range = widgets.Text(description = "Source IP Range: ", disabled=False, layout=widgets.Layout(width='40%'))
src_vm = widgets.Text(description = "Source VM: ", disabled=False, layout=widgets.Layout(width='40%'))
display(src_region, src_zone, src_network, src_ip_range, src_vm)

print("====== DESTINATION (ANALYST)======")
dst_project = widgets.Text(description = "Destination Project: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_network = widgets.Text(value='pc-network', description = "Destination Network: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_subnet = widgets.Text(value='pc-subnet', description = "Destination Subnet: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_subnet_range = widgets.Text(value='10.1.2.0/24', description = "Destination Subnet Range: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_instance_grp = widgets.Text(value='pc-grp', description = "Destination Instance Grp: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_vm = widgets.Text(value='pc-vm-1', description = "Destination VM: ", disabled=False, layout=widgets.Layout(width='40%'))
dst_ssh_ip = widgets.Text(description = "Destination SSH IP: ", disabled=False, layout=widgets.Layout(width='40%'))
display(dst_project, dst_network, dst_subnet, dst_subnet_range, dst_instance_grp, dst_vm, dst_ssh_ip, layout=widgets.Layout(width='40%'))

print("====== NETWORK ======")
mirror_network_tag = widgets.Text(value='pc-tag', description = "Mirror Network Tag: ", disabled=False, layout=widgets.Layout(width='40%'))
peering_name = widgets.Text(value='pc-peering', description = "VPC Peering Name: ", disabled=False, layout=widgets.Layout(width='40%'))
lb_hc_name = widgets.Text(value='pc-hc-ilb', description = "LB Health Check Name: ", disabled=False, layout=widgets.Layout(width='40%'))
lb_be_name = widgets.Text(value='pc-be-ilb', description = "LB Backend Name: ", disabled=False, layout=widgets.Layout(width='40%'))
lb_fe_name = widgets.Text(value='pc-fe-ilb', description = "LB Frontend Name: ", disabled=False, layout=widgets.Layout(width='40%'))
pm_policy_name = widgets.Text(value='pc-pmp', description = "Packet Mirroring Policy Name: ", disabled=False, layout=widgets.Layout(width='40%'))
display(mirror_network_tag, peering_name, lb_hc_name, lb_be_name, lb_fe_name, pm_policy_name)
# store user input in environment variables for use in subsequent comamnds
os.environ['SRC_REGION'] = src_region.value
os.environ['SRC_ZONE'] = src_zone.value
os.environ['SRC_NETWORK'] = src_network.value
os.environ['SRC_IP_RANGE'] = src_ip_range.value
os.environ['SRC_VM'] = src_vm.value

os.environ['DST_PROJECT'] = dst_project.value
os.environ['DST_NETWORK'] = dst_network.value
os.environ['DST_SUBNET'] = dst_subnet.value
os.environ['DST_SUBNET_RANGE'] = dst_subnet_range.value
os.environ['DST_INSTANCE_GRP'] = dst_instance_grp.value
os.environ['DST_SSH_IP'] = dst_ssh_ip.value
os.environ['DST_VM'] = dst_vm.value

os.environ['NETWORK_TAG'] = mirror_network_tag.value
os.environ['PEERING_NAME'] = peering_name.value

os.environ['FW_LB_RULE_NAME'] = dst_network.value + '-' + 'allow-lb-access'
os.environ['FW_HC_RULE_NAME'] = dst_network.value + '-' + 'allow-health-check'
os.environ['FW_SSH_RULE_NAME'] = dst_network.value + '-' + 'allow-ssh'
os.environ['FW_EGRESS_RULE_NAME'] = src_network.value + '-' + 'allow-all-egress'

os.environ['LB_HC_NAME'] = lb_hc_name.value
os.environ['LB_BE_NAME'] = lb_be_name.value
os.environ['LB_FE_NAME'] = lb_fe_name.value

os.environ['PM_POLICY_NAME'] = pm_policy_name.value

Create VPC Peering#

# create VPC network at analyst (dst) project
!gcloud compute networks create $DST_NETWORK \
    --project $DST_PROJECT \
    --subnet-mode=custom

print()

# create subnet in created VPC network at analyst (dst) project
!gcloud compute networks subnets create $DST_SUBNET \
    --project $DST_PROJECT \
    --network=$DST_NETWORK \
    --range=$DST_SUBNET_RANGE \
    --region=$SRC_REGION
# create VPC network peering connection from analyst (dst) project/network to target (src) project/network
!gcloud compute networks peerings create $PEERING_NAME \
    --project $DST_PROJECT \
    --network=$DST_NETWORK \
    --peer-project $SRC_PROJECT  \
    --peer-network $SRC_NETWORK

print()

# create VPC network peering connection from target (src) project/network to analyst (dst) project/network
!gcloud compute networks peerings create $PEERING_NAME \
    --project $SRC_PROJECT \
    --network=$SRC_NETWORK \
    --peer-project $DST_PROJECT  \
    --peer-network $DST_NETWORK

Setup Destination Collector#

# create VM instance group at analyst (dst) project
!gcloud compute instance-groups unmanaged create $DST_INSTANCE_GRP \
    --project $DST_PROJECT \
    --zone $SRC_ZONE
# following actions are performed at analyst (dst) project

# create VM instance for collection (modify as required)
!gcloud compute instances create $DST_VM \
    --project $DST_PROJECT \
    --zone $SRC_ZONE \
    --machine-type=e2-highmem-4 \
    --subnet=$DST_SUBNET \
    --tags=allow-ssh,allow-health-check \
    --create-disk=auto-delete=yes,boot=yes,device-name=$DST_VM,image-family=debian-10,image-project=debian-cloud,mode=rw,size=60,type=pd-ssd

print()

# add VM instance(s) to instance group
!gcloud compute instance-groups unmanaged add-instances $DST_INSTANCE_GRP \
    --project $DST_PROJECT \
    --zone=$SRC_ZONE \
    --instances=$DST_VM

# duplicate as required

Create Firewall Rules#

# create firewall rules at analyst (dst) project

# allow all ingress traffic from target (src) project/network/VM
!gcloud compute firewall-rules create $FW_LB_RULE_NAME \
    --project $DST_PROJECT \
    --network=$DST_NETWORK \
    --action=allow \
    --direction=ingress \
    --source-ranges=$SRC_IP_RANGE \
    --rules=tcp,udp,icmp

print()

# allow all ingress traffic from health check IPs
!gcloud compute firewall-rules create $FW_HC_RULE_NAME \
    --project $DST_PROJECT \
    --network=$DST_NETWORK \
    --action=allow \
    --direction=ingress \
    --target-tags=allow-health-check \
    --source-ranges=130.211.0.0/22,35.191.0.0/16 \
    --rules=tcp,udp,icmp

print()

# allow all ingress SSH traffic from specified IPs
!gcloud compute firewall-rules create $FW_SSH_RULE_NAME \
    --project $DST_PROJECT \
    --network=$DST_NETWORK \
    --action=allow \
    --direction=ingress \
    --target-tags=allow-ssh \
    --source-ranges=$DST_SSH_IP  \
    --rules=tcp,udp,icmp
# list egress firewall rules at target (src) project
!gcloud compute firewall-rules list \
    --project $SRC_PROJECT \
    --format='json' > src_fw_rules.json

with open('./src_fw_rules.json') as infile:
    src_fw_rules_df = pd.json_normalize(json.load(infile))

desired_columns = ['name', 'network', 'priority', 'direction', 'targetTags', 'destinationRanges', 'denied', 'allowed']
columns = list(set(src_fw_rules_df.columns) & set(desired_columns))

display(src_fw_rules_df[columns]
        .loc[src_fw_rules_df['direction'] == 'EGRESS']
        .loc[src_fw_rules_df['network'].str.contains(src_network.value)])
# [optional] create allow all egress firewall rule at target (src) project
!gcloud compute firewall-rules create $FW_EGRESS_RULE_NAME \
    --project $SRC_PROJECT \
    --network=$SRC_NETWORK \
    --action=allow \
    --direction=egress \
    --target-tags=$NETWORK_TAG \
    --destination-ranges=0.0.0.0/0 \
    --rules=tcp,udp,icmp

Implement Load Balancer#

# create a new regional HTTP health-check at analyst (dst) project
!gcloud compute health-checks create http $LB_HC_NAME \
    --project $DST_PROJECT \
    --region=$SRC_REGION \
    --port=80

print()

# create backend service at analyst (dst) project
!gcloud compute backend-services create $LB_BE_NAME \
    --project $DST_PROJECT \
    --load-balancing-scheme=internal \
    --protocol=tcp \
    --region=$SRC_REGION \
    --health-checks=$LB_HC_NAME \
    --health-checks-region=$SRC_REGION

print()

# add instance group to backend service at analyst (dst) project
!gcloud compute backend-services add-backend $LB_BE_NAME \
    --project $DST_PROJECT \
    --region=$SRC_REGION \
    --instance-group=$DST_INSTANCE_GRP \
    --instance-group-zone=$SRC_ZONE

print()

# create forwarding rule at analyst (dst) project
!gcloud compute forwarding-rules create $LB_FE_NAME \
    --project $DST_PROJECT \
    --region=$SRC_REGION \
    --load-balancing-scheme=internal \
    --backend-service=$LB_BE_NAME \
    --ports=all \
    --is-mirroring-collector \
    --network=$DST_NETWORK \
    --subnet=$DST_SUBNET

Implement Packet Mirroring#

# tag vm to capture packets from at target (src) project
!gcloud compute instances add-tags $SRC_VM \
    --project $SRC_PROJECT \
    --zone $SRC_ZONE \
    --tags $NETWORK_TAG

# duplicate as required
# create packet mirroring policy at analyst (dst) project
!gcloud compute packet-mirrorings create $PM_POLICY_NAME \
    --project $DST_PROJECT \
    --region=$SRC_REGION \
    --network=projects/$SRC_PROJECT/global/networks/$SRC_NETWORK \
    --mirrored-tags=$NETWORK_TAG \
    --collector-ilb=$LB_FE_NAME

Capture Packets#

  1. SSH into $DST_VM - gcloud compute ssh $DST_VM -project $DST_PROJECT -zone $SRC_ZONE

  2. Install tcpdump - sudo apt-get update, sudo apt install tcpdump -y

  3. sudo /usr/sbin/tcpdump port not 22 -w pc.pcap

Cleanup#

# at target (src) project

# delete firewall rule
!gcloud compute firewall-rules delete $FW_EGRESS_RULE_NAME --project $SRC_PROJECT --quiet

print()

# delete VPC peering
!gcloud compute networks peerings delete $PEERING_NAME \
    --project $SRC_PROJECT \
    --network=$SRC_NETWORK

print()

# remove tag from VM
!gcloud compute instances remove-tags $SRC_VM \
    --project $SRC_PROJECT \
    --zone $SRC_ZONE \
    --tags $NETWORK_TAG
# at analyst (dst) project

# delete packet mirroring policy
!gcloud compute packet-mirrorings delete $PM_POLICY_NAME --quiet \
    --project $DST_PROJECT \
    --region=$SRC_REGION

print()

# delete LB forwarding rule
!gcloud compute forwarding-rules delete $LB_FE_NAME --quiet \
    --project $DST_PROJECT \
    --region=$SRC_REGION

print()

# delete LB backend service
!gcloud compute backend-services delete $LB_BE_NAME --quiet \
    --project $DST_PROJECT \
    --region=$SRC_REGION

print()

# delete LB health check
!gcloud compute health-checks delete $LB_HC_NAME --quiet \
    --project $DST_PROJECT \
    --region=$SRC_REGION

print()

# delete firewall rules
!gcloud compute firewall-rules delete $FW_HC_RULE_NAME --project $DST_PROJECT --quiet
print()
!gcloud compute firewall-rules delete $FW_LB_RULE_NAME --project $DST_PROJECT --quiet
print()
!gcloud compute firewall-rules delete $FW_SSH_RULE_NAME --project $DST_PROJECT --quiet
print()

# delete VM(s)
!gcloud compute instances delete $DST_VM --quiet \
    --project $DST_PROJECT \
    --zone $SRC_ZONE

print()

# delete VM instance group
!gcloud compute instance-groups unmanaged delete $DST_INSTANCE_GRP --quiet \
    --project $DST_PROJECT \
    --zone $SRC_ZONE

print()

# delete subnet
!gcloud compute networks subnets delete $DST_SUBNET --quiet \
    --project $DST_PROJECT \
    --region $SRC_REGION

print()

# delete network
!gcloud compute networks delete $DST_NETWORK --project $DST_PROJECT --quiet

print()

# remove files
!rm ./src_fw_rules.json