Skip to content

Commit 329ac5d

Browse files
authored
Add GCP machinery (kevoreilly#2766)
1 parent c34f847 commit 329ac5d

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

conf/default/gcp.conf.default

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[gcp]
2+
# Specify the Google Cloud Zone (for example, europe-north2-a). This is case-sensitive
3+
zone = <zone_name>
4+
5+
# Specify the project identifier
6+
project = <project_id>
7+
8+
# Running in a GCP environment. If true, the Compute Engine credentials will be used
9+
running_in_gcp = true
10+
11+
# Specify the path to the service account key file. If not specified, the default service account will be used
12+
service_account_path =
13+
14+
# Specify a comma-separated list of available machines to be used.
15+
# Each machine will be represented by the instance-name (for example, cape-server-windows).
16+
# For each specified instance-name you have to define a dedicated section containing the details
17+
# on the respective machine. (E.g. cape-server-windows,cape-server-linux)
18+
# For better performance, it is recommended to leave this empty and set autoscale = yes.
19+
machines =
20+
21+
[cape-server-linux]
22+
# Specify the label name.
23+
# Label would be the instance-name of the current machine as specified in your GCP account.
24+
label = cape-server-linux
25+
26+
# Specify the operating system platform used by current machine
27+
# [windows/darwin/linux].
28+
platform = linux
29+
30+
# Set the machine architecture
31+
# x64 or x86
32+
arch = x64
33+
34+
# Specify the IP address of the current virtual machine. Make sure that the
35+
# IP address is valid and that the host machine is able to reach it. If not,
36+
# the analysis will fail.
37+
# ip =
38+
39+
# (Optional) Specify the name of the network interface that should be used
40+
# when dumping network traffic from this machine with tcpdump. If specified,
41+
# overrides the default interface specified above.
42+
# Example (eth0 is the interface name):
43+
# interface =
44+
45+
# (Optional) Specify the IP of the Result Server, as your virtual machine sees it.
46+
# The Result Server will always bind to the address and port specified in cuckoo.conf,
47+
# however you could set up your virtual network to use NAT/PAT, so you can specify here
48+
# the IP address for the Result Server as your machine sees it. If you don't specify an
49+
# address here, the machine will use the default value from cuckoo.conf.
50+
# NOTE: if you set this option you have to set result server IP to 0.0.0.0 in cuckoo.conf.
51+
# Example:
52+
# resultserver_ip =
53+
54+
# (Optional) Specify the port for the Result Server, as your virtual machine sees it.
55+
# The Result Server will always bind to the address and port specified in cuckoo.conf,
56+
# however you could set up your virtual network to use NAT/PAT, so you can specify here
57+
# the port for the Result Server as your machine sees it. If you don't specify a port
58+
# here, the machine will use the default value from cuckoo.conf.
59+
# resultserver_port =
60+
61+
# (Optional) Set your own tags. These are comma separated and help to identify
62+
# specific VMs. You can run samples on VMs with tag you require.
63+
# tags =

modules/machinery/gcp.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import logging
2+
3+
from lib.cuckoo.common.config import Config
4+
from typing import List
5+
6+
cfg = Config()
7+
HAVE_GCP = False
8+
if cfg.cuckoo.machinery == "gcp":
9+
try:
10+
from google.cloud import compute_v1
11+
from google.oauth2 import service_account
12+
from google.auth import compute_engine
13+
14+
HAVE_GCP = True
15+
except ImportError:
16+
pass
17+
18+
from lib.cuckoo.common.abstracts import Machinery
19+
from lib.cuckoo.common.exceptions import CuckooMachineError, CuckooDependencyError
20+
21+
log = logging.getLogger(__name__)
22+
23+
24+
class GCP(Machinery):
25+
26+
module_name = "gcp"
27+
28+
# VM states
29+
RUNNING = "RUNNING"
30+
PAUSED = "SUSPENDED"
31+
POWEROFF = "TERMINATED"
32+
PENDING = "PENDING"
33+
ABORTED = "ABORTED"
34+
ERROR = "ERROR"
35+
36+
def _initialize_check(self):
37+
"""Runs all checks when a machine manager is initialized.
38+
@raise CuckooDependencyError: if google-cloud-compute is not installed
39+
@raise CuckooMachineError: if configuration is invalid
40+
"""
41+
if not HAVE_GCP:
42+
raise CuckooDependencyError("Missed google-cloud-compute dependencies: poetry add google-cloud-compute")
43+
44+
# Read Configuration
45+
self.project = self.options.gcp.project
46+
self.zone = self.options.gcp.zone
47+
self.json_key_path = getattr(self.options.gcp, "service_account_path", None)
48+
self.running_in_gcp = getattr(self.options.gcp, "running_in_gcp", False)
49+
50+
log.info("Connecting to GCP Project: %s, Zone: %s", self.project, self.zone)
51+
52+
# Initialize Clients
53+
if self.json_key_path:
54+
creds = service_account.Credentials.from_service_account_file(self.json_key_path)
55+
self.instances_client = compute_v1.InstancesClient(credentials=creds)
56+
elif self.running_in_gcp:
57+
log.info("Using Compute Engine credentials")
58+
creds = compute_engine.Credentials()
59+
self.instances_client = compute_v1.InstancesClient(credentials=creds)
60+
else:
61+
log.info("No Service Account JSON provided; using Application Default Credentials")
62+
self.instances_client = compute_v1.InstancesClient()
63+
64+
super()._initialize_check()
65+
66+
def _list(self) -> List[str]:
67+
"""Lists virtual machines configured.
68+
"""
69+
try:
70+
request = compute_v1.ListInstancesRequest(
71+
project=self.project,
72+
zone=self.zone,
73+
)
74+
instances = self.instances_client.list(request=request)
75+
return [instance.name for instance in instances]
76+
except Exception as e:
77+
raise CuckooMachineError(f"Failed to list instances in project '{self.project}' and zone '{self.zone}': {e}") from e
78+
79+
def _status(self, label) -> str:
80+
"""
81+
Get current status of a VM
82+
@param label: virtual machine label
83+
@return: status string
84+
"""
85+
try:
86+
request = compute_v1.GetInstanceRequest(
87+
project=self.project,
88+
zone=self.zone,
89+
instance=label
90+
)
91+
instance = self.instances_client.get(request=request)
92+
except Exception as e:
93+
raise CuckooMachineError(f"Error getting status for machine '{label}': {e}") from e
94+
95+
# Reference: https://docs.cloud.google.com/compute/docs/instances/instance-lifecycle
96+
if instance.status in {"PENDING", "PROVISIONING", "STAGING", "REPAIRING"}:
97+
return self.PENDING
98+
elif instance.status == "RUNNING":
99+
return self.RUNNING
100+
elif instance.status in {"SUSPENDED", "SUSPENDING"}:
101+
return self.PAUSED
102+
elif instance.status == "TERMINATED":
103+
return self.POWEROFF
104+
elif instance.status in {"STOPPING", "PENDING_STOP"}:
105+
return self.ABORTED
106+
else:
107+
return self.ERROR
108+
109+
def start(self, label):
110+
"""
111+
Start a virtual machine.
112+
@param label: virtual machine label.
113+
@raise CuckooMachineError: if unable to start.
114+
"""
115+
log.debug("Starting VM %s", label)
116+
try:
117+
if self._status(label) in (self.RUNNING, self.PENDING):
118+
log.warning("Trying to start a machine that is already running or pending: %s", label)
119+
return
120+
121+
request = compute_v1.StartInstanceRequest(
122+
project=self.project,
123+
zone=self.zone,
124+
instance=label
125+
)
126+
self.instances_client.start(request=request)
127+
except Exception as e:
128+
raise CuckooMachineError(f"Unable to start machine '{label}': {e}") from e
129+
130+
def stop(self, label):
131+
"""
132+
Stop a virtual machine.
133+
@param label: virtual machine label.
134+
@raise CuckooMachineError: if unable to stop.
135+
"""
136+
log.debug("Stopping VM %s", label)
137+
try:
138+
if self._status(label) == self.POWEROFF:
139+
log.warning("Trying to stop a machine that is already stopped: %s", label)
140+
return
141+
142+
request = compute_v1.StopInstanceRequest(
143+
project=self.project,
144+
zone=self.zone,
145+
instance=label
146+
)
147+
self.instances_client.stop(request=request)
148+
except Exception as e:
149+
raise CuckooMachineError(f"Unable to stop machine '{label}': {e}") from e

0 commit comments

Comments
 (0)