Skip to content

Commit b326ae0

Browse files
Update to port scan to use cloud logs
1 parent 8c041d8 commit b326ae0

File tree

3 files changed

+205
-7
lines changed

3 files changed

+205
-7
lines changed

jobs/account-port-scan/.mise.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[tools]
2+
python = "3.12"
3+
usage = "latest"
4+
5+
[env]
6+
_.python.venv = { path = ".venv", create = true }
7+
PROJECT_NAME = "{{ config_root | basename }}"
8+
9+
[tasks."uv:reqs"]
10+
description = "Install dependencies from requirements file"
11+
alias = "uvr"
12+
run = "uv pip install -r requirements.txt"
13+
14+
[tasks."uv:freeze"]
15+
description = "Create requirements.txt from currently installed modules"
16+
alias = "uvf"
17+
run = "uv pip freeze > requirements.txt"
18+
19+
[tasks."uv:install"]
20+
description = "Install pip packages"
21+
alias = "uvi"
22+
run = "uv pip install"
23+
24+
[tasks.info]
25+
description = "Print project information"
26+
run = '''
27+
echo "Project: $PROJECT_NAME"
28+
echo "Virtual Environment: $VIRTUAL_ENV"
29+
'''

jobs/account-port-scan/Dockerfile

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,56 @@
1+
# FROM python:3.12-slim
2+
3+
# WORKDIR /usr/src/app
4+
5+
# # Install uv
6+
# RUN pip install --upgrade pip && \
7+
# pip install uv
8+
9+
# # Copy requirements file
10+
# COPY requirements.txt ./
11+
12+
# # Create virtual environment and install dependencies with uv in one step
13+
# RUN uv venv /usr/src/app/venv && \
14+
# uv pip install --python=/usr/src/app/venv/bin/python --no-cache -r requirements.txt
15+
16+
# # Copy application files
17+
# COPY port-scan-report.py ./app.py
18+
# COPY logging.json ./logging.json
19+
20+
# # Make the script executable
21+
# RUN chmod +x app.py
22+
23+
# # Set PATH to use the Python from the virtual environment
24+
# ENV PATH="/usr/src/app/venv/bin:$PATH"
25+
26+
# # Default command
27+
# ENTRYPOINT ["./app.py"]
28+
129
FROM python:3.12-slim
230

331
WORKDIR /usr/src/app
432

5-
COPY requirements.txt ./
33+
# Install uv
634
RUN pip install --upgrade pip && \
7-
pip install --no-cache-dir -r requirements.txt
35+
pip install uv
836

37+
# Copy requirements file
38+
COPY requirements.txt ./
39+
40+
# Create virtual environment and install dependencies with uv
41+
RUN uv venv && \
42+
. .venv/bin/activate && \
43+
uv pip install --no-cache -r requirements.txt
44+
45+
# Copy application files
946
COPY port-scan-report.py ./app.py
1047
COPY logging.json ./logging.json
1148

12-
# Copy the entrypoint script and make it executable
49+
# Make the script executable
1350
RUN chmod +x app.py
1451

52+
# Set PATH to use the Python from the virtual environment
53+
ENV PATH="/usr/src/app/.venv/bin:$PATH"
54+
1555
# Default command
16-
ENTRYPOINT [ "./app.py" ]
56+
ENTRYPOINT ["./app.py"]

jobs/account-port-scan/port-scan-report.py

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,33 @@
1717
import sys
1818
import os
1919
import socket
20+
import json
2021
import logging
22+
import logging.config
23+
import requests
2124
import SoftLayer
2225
import ibm_vpc
2326
from ibm_cloud_sdk_core import ApiException
2427
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
2528

2629
"""
27-
Pull IBM Cloud API key from environment. If not set, raise an error.
30+
Pull IBM Cloud API key and Logging endpoint from environment. If not set, raise an error.
2831
"""
2932
ibmcloud_api_key = os.environ.get('IBMCLOUD_API_KEY')
3033
if not ibmcloud_api_key:
3134
raise ValueError("IBMCLOUD_API_KEY environment variable not found")
3235

36+
cloud_logging_endpoint = os.environ.get('IBM_CLOUD_LOGGING_ENDPOINT')
37+
if not cloud_logging_endpoint:
38+
raise ValueError("IBM_CLOUD_LOGGING_ENDPOINT environment variable not found")
39+
3340
"""
3441
Create an IAM authenticator object for use with the VPC API.
3542
"""
3643
authenticator = IAMAuthenticator(apikey=ibmcloud_api_key)
3744

3845

39-
def setup_logging(default_path='logging.json', default_level=logging.info, env_key='LOG_CFG'):
46+
def setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'):
4047
"""
4148
Set up logging configuration and use logging.json to format logs
4249
"""
@@ -148,14 +155,115 @@ def scan_top_ports(target):
148155
return open_ports
149156

150157

158+
def get_iam_token():
159+
"""
160+
Get IAM token using the API key for authenticating with IBM Cloud Logging
161+
"""
162+
url = "https://iam.cloud.ibm.com/identity/token"
163+
headers = {
164+
"Content-Type": "application/x-www-form-urlencoded",
165+
"Accept": "application/json"
166+
}
167+
data = {
168+
"grant_type": "urn:ibm:params:oauth:grant-type:apikey",
169+
"apikey": ibmcloud_api_key
170+
}
171+
172+
try:
173+
response = requests.post(url, headers=headers, data=data)
174+
response.raise_for_status()
175+
token_data = response.json()
176+
return token_data["access_token"]
177+
except requests.exceptions.RequestException as e:
178+
logging.error("Error obtaining IAM token: %s", e)
179+
return None
180+
181+
182+
def format_scan_results(target_type, results):
183+
"""
184+
Format scan results to be sent to IBM Cloud Logging
185+
186+
Args:
187+
target_type (str): Type of target (floating_ip, virtual_guest, bare_metal)
188+
results (dict): Dictionary with IP addresses as keys and lists of open ports as values
189+
190+
Returns:
191+
list: List of formatted log entries
192+
"""
193+
log_entries = []
194+
computer_name = os.environ.get('CE_PROJECT_ID', socket.gethostname())
195+
for ip, ports in results.items():
196+
if ports: # Only include IPs with open ports
197+
log_entry = {
198+
"applicationName": "account-port-scan",
199+
"subsystemName": f"{target_type}-scan",
200+
"computerName": computer_name,
201+
"text": {
202+
"ip_address": ip,
203+
"open_ports": ports,
204+
"target_type": target_type,
205+
"message": f"Open ports detected on {target_type} {ip}: {ports}"
206+
}
207+
}
208+
log_entries.append(log_entry)
209+
210+
return log_entries
211+
212+
213+
def send_to_ibm_cloud_logging(log_entries):
214+
"""
215+
Send log entries to IBM Cloud Logging
216+
217+
Args:
218+
log_entries (list): List of formatted log entries
219+
220+
Returns:
221+
bool: True if logs were sent successfully, False otherwise
222+
"""
223+
if not log_entries:
224+
logging.info("No open ports detected, no logs to send")
225+
return True
226+
227+
# Get log endpoint from environment variable or use default from example
228+
log_endpoint = f"{cloud_logging_endpoint}/logs/v1/singles"
229+
230+
token = get_iam_token()
231+
if not token:
232+
return False
233+
234+
headers = {
235+
"Content-Type": "application/json",
236+
"Authorization": f"Bearer {token}"
237+
}
238+
239+
try:
240+
response = requests.post(log_endpoint, headers=headers, json=log_entries)
241+
response.raise_for_status()
242+
logging.info("Successfully sent %d log entries to IBM Cloud Logging", len(log_entries))
243+
return True
244+
except requests.exceptions.RequestException as e:
245+
logging.error("Error sending logs to IBM Cloud Logging: %s", e)
246+
return False
247+
248+
151249
def main():
152250
"""
153251
Main function to scan IBM Cloud VPC and classic infrastructure
252+
and send results to IBM Cloud Logging
154253
"""
254+
# Set up logging
255+
setup_logging()
256+
257+
# Results dictionary for each target type
258+
floating_ip_results = {}
259+
virtual_guest_results = {}
260+
bare_metal_results = {}
261+
155262
print("Starting scan of floating IPs...")
156263
targets = get_floating_ips()
157264
for target in targets:
158265
open_ports = scan_top_ports(target)
266+
floating_ip_results[target] = open_ports
159267
if open_ports:
160268
print(f"Open ports on {target}: {open_ports}")
161269
print("VPC Floating IP Scan complete.")
@@ -164,6 +272,7 @@ def main():
164272
targets = get_classic_infrastructure_instances()
165273
for target in targets:
166274
open_ports = scan_top_ports(target)
275+
virtual_guest_results[target] = open_ports
167276
if open_ports:
168277
print(f"Open ports on {target}: {open_ports}")
169278
print("Classic Virtual Guests Scan complete.")
@@ -172,10 +281,30 @@ def main():
172281
targets = get_classic_infrastructure_hardware()
173282
for target in targets:
174283
open_ports = scan_top_ports(target)
284+
bare_metal_results[target] = open_ports
175285
if open_ports:
176286
print(f"Open ports on {target}: {open_ports}")
177287
print("Classic Bare Metals Scan complete.")
288+
289+
# Format and send results to IBM Cloud Logging
290+
floating_ip_logs = format_scan_results("floating_ip", floating_ip_results)
291+
virtual_guest_logs = format_scan_results("virtual_guest", virtual_guest_results)
292+
bare_metal_logs = format_scan_results("bare_metal", bare_metal_results)
293+
294+
# Combine all logs into a single list
295+
all_logs = floating_ip_logs + virtual_guest_logs + bare_metal_logs
296+
297+
# Send logs to IBM Cloud Logging
298+
if all_logs:
299+
print(f"Sending {len(all_logs)} log entries to IBM Cloud Logging...")
300+
success = send_to_ibm_cloud_logging(all_logs)
301+
if success:
302+
print("Successfully sent logs to IBM Cloud Logging")
303+
else:
304+
print("Failed to send logs to IBM Cloud Logging")
305+
else:
306+
print("No open ports detected, no logs to send")
178307

179308

180309
if __name__ == "__main__":
181-
main()
310+
main()

0 commit comments

Comments
 (0)