mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-07-18 14:53:40 +02:00
Containers now start on boot via restart_policy unless-stopped, so a highstate is no longer required to bring them up. Gather and display the container table even when no highstate has completed since reboot, while still warning the user. The exit code / JSON status_code stays 2 in that state so SOC's Grid continues to show the restarting message unchanged.
189 lines
6.4 KiB
Python
Executable File
189 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
# Elastic License 2.0.
|
|
|
|
import json
|
|
import os
|
|
from rich import box
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
EXPECTED_CONTAINERS_FILE = "/opt/so/conf/so-status/so-status.conf"
|
|
HIGHSTATE_COMPLETE_FILE = "/opt/so/log/salt/lasthighstate"
|
|
UPTIME_FILE = "/proc/uptime"
|
|
|
|
def showUsage(options, args):
|
|
print('Usage: {} [OPTIONS]'.format(sys.argv[0]))
|
|
print(' Options:')
|
|
print(' -h - Prints this usage information')
|
|
print(' -q - Suppress output; useful for automation of exit code value')
|
|
print(' -j - Output in JSON format')
|
|
print(' -i - Consider the installation outcome regardless of whether the system appears healthy')
|
|
print('')
|
|
print(' Exit codes:')
|
|
print(' 0 - Success, system appears to be running correctly')
|
|
print(' 1 - Error, one or more subsystems are not running')
|
|
print(' 2 - System is starting')
|
|
print(' 99 - Installation in progress')
|
|
print(' 100 - System installation encountered errors')
|
|
sys.exit(1)
|
|
|
|
|
|
def fail(msg):
|
|
print(msg, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def check_installation_status(options, console):
|
|
if "-i" in options:
|
|
if os.path.isfile('/root/failure'):
|
|
return 100
|
|
if os.path.isfile('/root/success'):
|
|
return 0
|
|
return 99
|
|
return 0
|
|
|
|
|
|
def check_system_status(options, console):
|
|
code = 0
|
|
highstate_end_time = 0
|
|
try:
|
|
highstate_end_time = os.path.getmtime(HIGHSTATE_COMPLETE_FILE)
|
|
uptime_file = open(UPTIME_FILE, "r")
|
|
uptime_contents = uptime_file.readline()
|
|
uptime = uptime_contents.split()
|
|
if len(uptime) != 2:
|
|
fail("Unable to determine system uptime")
|
|
system_start_time = time.time() - float(uptime[0])
|
|
if highstate_end_time < system_start_time:
|
|
code = 2
|
|
except OSError:
|
|
code = 99
|
|
|
|
return code
|
|
|
|
|
|
def output(options, console, code, data):
|
|
if "-j" in options:
|
|
summary = { "status_code": code, "containers": data }
|
|
print(json.dumps(summary))
|
|
elif "-q" not in options:
|
|
if code == 99:
|
|
console.print(" [bold red]:exclamation: [bold white]Installation does not appear to be complete. A highstate has not fully completed.")
|
|
elif code == 100:
|
|
console.print(" [bold red]:exclamation: [bold white]Installation encountered errors.")
|
|
else:
|
|
if code == 2:
|
|
console.print(" [bold yellow]:hourglass: [bold white]System appears to be starting. No highstate has completed since the system was restarted. Container status is shown below.")
|
|
table = Table(title = "Security Onion Status", show_edge = False, safe_box = True, box = box.MINIMAL)
|
|
table.add_column("Container", justify="right", style="white", no_wrap=True)
|
|
table.add_column("Status", justify="left", style="green", no_wrap=True)
|
|
table.add_column("Details", justify="right", style="cyan", no_wrap=True)
|
|
data.sort(key = lambda x: x['Name'])
|
|
for container in data:
|
|
color = "[green]"
|
|
if container['Status'] != "running":
|
|
color = "[bold red]"
|
|
table.add_row(container['Name'], color + container['Status'], container['Details'])
|
|
console.print()
|
|
console.print(table)
|
|
console.print()
|
|
if code == 0:
|
|
console.print(" [green]:heavy_check_mark: [bold white]This onion is ready to make your adversaries cry!")
|
|
elif code == 1:
|
|
console.print(" [bold red]:exclamation: [bold white]Check container logs and /opt/so/log for more details.")
|
|
console.print()
|
|
|
|
def check_container_status(options, console):
|
|
code = 0
|
|
cli = "docker"
|
|
proc = subprocess.run([cli, 'ps', '--format', 'json'], stdout=subprocess.PIPE, encoding="utf-8")
|
|
if proc.returncode != 0:
|
|
fail("Container system error; unable to obtain container process statuses")
|
|
|
|
container_list = []
|
|
expected_file = open(EXPECTED_CONTAINERS_FILE, "r")
|
|
for container in expected_file:
|
|
if container.startswith('#'):
|
|
continue
|
|
container = container.strip()
|
|
exists = False
|
|
details = { "Name": container, "Status": "missing", "Details": ""}
|
|
|
|
try:
|
|
# Podman format
|
|
containers_data = json.loads(proc.stdout)
|
|
for item in containers_data:
|
|
if item['Names'][0] == container:
|
|
details['Status'] = item['State']
|
|
details['Details'] = item['Status']
|
|
container_list.append(details)
|
|
exists = True
|
|
if item['State'] != "running":
|
|
code = 1
|
|
break
|
|
|
|
except:
|
|
# Docker format
|
|
for line in proc.stdout.splitlines():
|
|
if len(line) > 2:
|
|
item = json.loads(line)
|
|
if item['Names'] == container:
|
|
details['Status'] = item['State']
|
|
details['Details'] = item['Status']
|
|
container_list.append(details)
|
|
exists = True
|
|
if item['State'] != "running":
|
|
code = 1
|
|
break
|
|
|
|
if not exists:
|
|
container_list.append(details)
|
|
code = 1
|
|
return code, container_list
|
|
|
|
|
|
def check_status(options, console):
|
|
container_list = []
|
|
code = check_installation_status(options, console)
|
|
if code == 0:
|
|
code = check_system_status(options, console)
|
|
# Containers now start on boot without a highstate, so gather/display their
|
|
# status even when the system is still "starting" (code 2). Keep the starting
|
|
# code as the exit/status_code so SOC keeps showing the "restarting" message
|
|
# on the Grid until a highstate completes.
|
|
if code == 0 or code == 2:
|
|
container_code, container_list = check_container_status(options, console)
|
|
if code == 0:
|
|
code = container_code
|
|
output(options, console, code, container_list)
|
|
return code
|
|
|
|
|
|
def main():
|
|
options = []
|
|
args = sys.argv[1:]
|
|
for option in args:
|
|
if option.startswith("-"):
|
|
options.append(option)
|
|
|
|
if "-h" in options or "--help" in options or "-?" in options:
|
|
showUsage(options, None)
|
|
|
|
proc = subprocess.run(['id', '-u'], stdout=subprocess.PIPE, encoding="utf-8")
|
|
if proc.stdout.strip() != "0":
|
|
fail("This program must be run as root")
|
|
|
|
console = Console()
|
|
sys.exit(check_status(options, console))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|