Add lab-02-security-as-a-system
This commit is contained in:
1 parent
dcf58ecf2a
commit
3213ab1435
10 files changed
+652
No files matched your search
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from service_common import record_event
|
||||
|
||||
|
||||
HOST = os.environ["MINICORP_SERVICE_HOST"]
|
||||
PORT = int(os.environ["MINICORP_SERVICE_PORT"])
|
||||
|
||||
SERVICE_ID = os.environ["MINICORP_SERVICE_ID"]
|
||||
SERVICE_TOKEN = os.environ["MINICORP_SERVICE_TOKEN"]
|
||||
|
||||
EVIDENCE_LOG = os.environ["MINICORP_EVIDENCE_LOG"]
|
||||
|
||||
|
||||
class AdminHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path != "/":
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
source = self.client_address[0]
|
||||
|
||||
record_event(
|
||||
EVIDENCE_LOG,
|
||||
service="admin",
|
||||
service_token=SERVICE_TOKEN,
|
||||
source=source,
|
||||
port=PORT,
|
||||
)
|
||||
|
||||
body = (
|
||||
"MiniCorp Administration Service\n"
|
||||
f"service_id: {SERVICE_ID}\n"
|
||||
"environment: production\n"
|
||||
"debug: enabled\n"
|
||||
"backend: inventory-api\n"
|
||||
).encode("utf-8")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header(
|
||||
"Content-Type",
|
||||
"text/plain; charset=utf-8",
|
||||
)
|
||||
self.send_header(
|
||||
"Content-Length",
|
||||
str(len(body)),
|
||||
)
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
server = ThreadingHTTPServer(
|
||||
(HOST, PORT),
|
||||
AdminHandler,
|
||||
)
|
||||
|
||||
print(
|
||||
f"MiniCorp admin service listening on {HOST}:{PORT}"
|
||||
)
|
||||
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Lab 2: Security as a System
|
||||
# Scenario-specific configuration.
|
||||
#
|
||||
# Reusable network topology and addressing are provided by shared/minicorp.
|
||||
|
||||
LAB_ID="lab-02-security-as-a-system"
|
||||
LAB_NAME="Security as a System"
|
||||
|
||||
# MiniCorp topology used by this scenario.
|
||||
LAB_TOPOLOGY="client-router-server"
|
||||
|
||||
#
|
||||
# Persistent environment state
|
||||
#
|
||||
|
||||
LAB_STATE_DIR="/var/lib/minicorp/$LAB_ID"
|
||||
LAB_IDENTITY_FILE="$LAB_STATE_DIR/service-identities"
|
||||
|
||||
#
|
||||
# Investigation evidence
|
||||
#
|
||||
|
||||
# Evidence must be available to Codio's secure assessment environment,
|
||||
# which receives the student's workspace.
|
||||
LAB_EVIDENCE_DIR="/home/codio/workspace/.minicorp-lab2"
|
||||
LAB_EVIDENCE_LOG="$LAB_EVIDENCE_DIR/events.jsonl"
|
||||
|
||||
#
|
||||
# MiniCorp services
|
||||
#
|
||||
|
||||
LAB_WEB_PORT="8000"
|
||||
LAB_STATUS_PORT="8081"
|
||||
LAB_ADMIN_PORT="9090"
|
||||
|
||||
LAB_WEB_STDOUT="/tmp/minicorp-lab2-web.stdout"
|
||||
LAB_STATUS_STDOUT="/tmp/minicorp-lab2-status.stdout"
|
||||
LAB_ADMIN_STDOUT="/tmp/minicorp-lab2-admin.stdout"
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
|
||||
MODULUS = 1 << 24
|
||||
FACTOR = 5
|
||||
BIAS = 0x456789
|
||||
FACTOR_INVERSE = pow(FACTOR, -1, MODULUS)
|
||||
|
||||
|
||||
def pack(value):
|
||||
number = int(value, 16)
|
||||
result = (number * FACTOR + BIAS) % MODULUS
|
||||
return f"{result:06x}"
|
||||
|
||||
|
||||
def unpack(value):
|
||||
number = int(value, 16)
|
||||
result = ((number - BIAS) * FACTOR_INVERSE) % MODULUS
|
||||
return f"{result:06x}"
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
raise SystemExit(2)
|
||||
|
||||
operation = sys.argv[1]
|
||||
value = sys.argv[2].lower()
|
||||
|
||||
if len(value) != 6:
|
||||
raise SystemExit(2)
|
||||
|
||||
if operation == "pack":
|
||||
print(pack(value))
|
||||
elif operation == "unpack":
|
||||
print(unpack(value))
|
||||
else:
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
printf 'ERROR: init_state.sh must run as root.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#
|
||||
# Persistent environment identity
|
||||
#
|
||||
|
||||
mkdir -p "$LAB_STATE_DIR"
|
||||
chmod 700 "$LAB_STATE_DIR"
|
||||
|
||||
if [[ ! -f "$LAB_IDENTITY_FILE" ]]; then
|
||||
web_value="$(openssl rand -hex 3)"
|
||||
api_value="$(openssl rand -hex 3)"
|
||||
admin_value="$(openssl rand -hex 3)"
|
||||
|
||||
web_token="$(
|
||||
python3 "$SCRIPT_DIR/identity.py" pack "$web_value"
|
||||
)"
|
||||
|
||||
api_token="$(
|
||||
python3 "$SCRIPT_DIR/identity.py" pack "$api_value"
|
||||
)"
|
||||
|
||||
admin_token="$(
|
||||
python3 "$SCRIPT_DIR/identity.py" pack "$admin_value"
|
||||
)"
|
||||
|
||||
cat > "$LAB_IDENTITY_FILE" <<STATE
|
||||
LAB_WEB_SERVICE_TOKEN="web-$web_token"
|
||||
LAB_API_SERVICE_TOKEN="api-$api_token"
|
||||
LAB_ADMIN_SERVICE_TOKEN="adm-$admin_token"
|
||||
STATE
|
||||
|
||||
chown root:root "$LAB_IDENTITY_FILE"
|
||||
chmod 600 "$LAB_IDENTITY_FILE"
|
||||
fi
|
||||
|
||||
#
|
||||
# Investigation evidence
|
||||
#
|
||||
|
||||
mkdir -p "$LAB_EVIDENCE_DIR"
|
||||
|
||||
if [[ ! -f "$LAB_EVIDENCE_LOG" ]]; then
|
||||
touch "$LAB_EVIDENCE_LOG"
|
||||
fi
|
||||
|
||||
# Keep the evidence readable by the Codio user and secure grader.
|
||||
chown codio:codio "$LAB_EVIDENCE_DIR"
|
||||
chown codio:codio "$LAB_EVIDENCE_LOG"
|
||||
|
||||
chmod 700 "$LAB_EVIDENCE_DIR"
|
||||
chmod 600 "$LAB_EVIDENCE_LOG"
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LAB_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPO_ROOT="$(cd "$LAB_DIR/../.." && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
source "$REPO_ROOT/shared/minicorp/lib/minicorp.sh"
|
||||
source "$REPO_ROOT/shared/minicorp/topologies/client-router-server.sh"
|
||||
|
||||
printf '\n'
|
||||
mc_log "Resetting $LAB_NAME"
|
||||
|
||||
#
|
||||
# Remove runtime
|
||||
#
|
||||
|
||||
mc_topology_client_router_server_down
|
||||
|
||||
rm -f \
|
||||
"$LAB_WEB_STDOUT" \
|
||||
"$LAB_STATUS_STDOUT" \
|
||||
"$LAB_ADMIN_STDOUT"
|
||||
|
||||
#
|
||||
# Clear investigation evidence
|
||||
#
|
||||
|
||||
if [[ -f "$LAB_EVIDENCE_LOG" ]]; then
|
||||
: > "$LAB_EVIDENCE_LOG"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
mc_log "$LAB_NAME has been reset."
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def record_event(
|
||||
evidence_log,
|
||||
*,
|
||||
service,
|
||||
service_token,
|
||||
source,
|
||||
port,
|
||||
result="ok",
|
||||
**extra,
|
||||
):
|
||||
entry = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"service": service,
|
||||
"service_token": service_token,
|
||||
"source": source,
|
||||
"port": port,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
entry.update(extra)
|
||||
|
||||
line = (
|
||||
json.dumps(
|
||||
entry,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
fd = os.open(
|
||||
evidence_log,
|
||||
os.O_WRONLY | os.O_CREAT | os.O_APPEND,
|
||||
0o600,
|
||||
)
|
||||
|
||||
try:
|
||||
os.write(fd, line.encode("utf-8"))
|
||||
finally:
|
||||
os.close(fd)
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LAB_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPO_ROOT="$(cd "$LAB_DIR/../.." && pwd)"
|
||||
|
||||
source "$SCRIPT_DIR/config.sh"
|
||||
source "$REPO_ROOT/shared/minicorp/lib/minicorp.sh"
|
||||
source "$REPO_ROOT/shared/minicorp/topologies/client-router-server.sh"
|
||||
|
||||
printf '\n'
|
||||
mc_log "Setting up $LAB_NAME"
|
||||
|
||||
#
|
||||
# Configuration
|
||||
#
|
||||
|
||||
if [[ "$LAB_TOPOLOGY" != "client-router-server" ]]; then
|
||||
mc_error "Unsupported topology: $LAB_TOPOLOGY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#
|
||||
# Required tools
|
||||
#
|
||||
|
||||
for command_name in \
|
||||
ip \
|
||||
sysctl \
|
||||
openssl \
|
||||
python3 \
|
||||
ss
|
||||
do
|
||||
mc_require_command "$command_name"
|
||||
done
|
||||
|
||||
#
|
||||
# Persistent state
|
||||
#
|
||||
|
||||
mc_log "Initializing MiniCorp lab state"
|
||||
|
||||
"$SCRIPT_DIR/init_state.sh"
|
||||
|
||||
source "$LAB_IDENTITY_FILE"
|
||||
|
||||
#
|
||||
# Runtime service identities
|
||||
#
|
||||
|
||||
LAB_WEB_SERVICE_ID="web-$(
|
||||
python3 "$SCRIPT_DIR/identity.py" \
|
||||
unpack "${LAB_WEB_SERVICE_TOKEN#web-}"
|
||||
)"
|
||||
|
||||
LAB_API_SERVICE_ID="api-$(
|
||||
python3 "$SCRIPT_DIR/identity.py" \
|
||||
unpack "${LAB_API_SERVICE_TOKEN#api-}"
|
||||
)"
|
||||
|
||||
LAB_ADMIN_SERVICE_ID="adm-$(
|
||||
python3 "$SCRIPT_DIR/identity.py" \
|
||||
unpack "${LAB_ADMIN_SERVICE_TOKEN#adm-}"
|
||||
)"
|
||||
|
||||
#
|
||||
# Clean stale runtime
|
||||
#
|
||||
|
||||
mc_log "Clearing stale MiniCorp runtime"
|
||||
|
||||
mc_topology_client_router_server_down \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
rm -f \
|
||||
"$LAB_WEB_STDOUT" \
|
||||
"$LAB_STATUS_STDOUT" \
|
||||
"$LAB_ADMIN_STDOUT"
|
||||
|
||||
#
|
||||
# Build network
|
||||
#
|
||||
|
||||
mc_log "Building MiniCorp virtual network"
|
||||
|
||||
mc_topology_client_router_server_up \
|
||||
>/dev/null
|
||||
|
||||
if ! mc_topology_client_router_server_check \
|
||||
>/dev/null
|
||||
then
|
||||
mc_error "MiniCorp topology validation failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#
|
||||
# Start services
|
||||
#
|
||||
|
||||
mc_log "Starting MiniCorp services"
|
||||
|
||||
sudo ip netns exec "$MC_SERVER_NS" env \
|
||||
MINICORP_SERVICE_HOST="$MC_SERVER_ADDR" \
|
||||
MINICORP_SERVICE_PORT="$LAB_WEB_PORT" \
|
||||
MINICORP_SERVICE_ID="$LAB_WEB_SERVICE_ID" \
|
||||
MINICORP_SERVICE_TOKEN="$LAB_WEB_SERVICE_TOKEN" \
|
||||
MINICORP_EVIDENCE_LOG="$LAB_EVIDENCE_LOG" \
|
||||
python3 -u "$SCRIPT_DIR/web_service.py" \
|
||||
>"$LAB_WEB_STDOUT" 2>&1 &
|
||||
|
||||
sudo ip netns exec "$MC_SERVER_NS" env \
|
||||
MINICORP_SERVICE_HOST="$MC_SERVER_ADDR" \
|
||||
MINICORP_SERVICE_PORT="$LAB_STATUS_PORT" \
|
||||
MINICORP_SERVICE_ID="$LAB_API_SERVICE_ID" \
|
||||
MINICORP_SERVICE_TOKEN="$LAB_API_SERVICE_TOKEN" \
|
||||
MINICORP_EVIDENCE_LOG="$LAB_EVIDENCE_LOG" \
|
||||
python3 -u "$SCRIPT_DIR/status_service.py" \
|
||||
>"$LAB_STATUS_STDOUT" 2>&1 &
|
||||
|
||||
sudo ip netns exec "$MC_SERVER_NS" env \
|
||||
MINICORP_SERVICE_HOST="$MC_SERVER_ADDR" \
|
||||
MINICORP_SERVICE_PORT="$LAB_ADMIN_PORT" \
|
||||
MINICORP_SERVICE_ID="$LAB_ADMIN_SERVICE_ID" \
|
||||
MINICORP_SERVICE_TOKEN="$LAB_ADMIN_SERVICE_TOKEN" \
|
||||
MINICORP_EVIDENCE_LOG="$LAB_EVIDENCE_LOG" \
|
||||
python3 -u "$SCRIPT_DIR/admin_service.py" \
|
||||
>"$LAB_ADMIN_STDOUT" 2>&1 &
|
||||
|
||||
#
|
||||
# Verify listeners
|
||||
#
|
||||
|
||||
for port in \
|
||||
"$LAB_WEB_PORT" \
|
||||
"$LAB_STATUS_PORT" \
|
||||
"$LAB_ADMIN_PORT"
|
||||
do
|
||||
ready=0
|
||||
|
||||
for attempt in {1..20}; do
|
||||
if mc_exec "$MC_SERVER_NS" ss -ltn |
|
||||
grep -F "$MC_SERVER_ADDR:$port" \
|
||||
>/dev/null
|
||||
then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if [[ "$ready" -ne 1 ]]; then
|
||||
mc_error \
|
||||
"A required MiniCorp service failed to start."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
mc_log "$LAB_NAME is ready."
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from service_common import record_event
|
||||
|
||||
|
||||
HOST = os.environ["MINICORP_SERVICE_HOST"]
|
||||
PORT = int(os.environ["MINICORP_SERVICE_PORT"])
|
||||
|
||||
SERVICE_ID = os.environ["MINICORP_SERVICE_ID"]
|
||||
SERVICE_TOKEN = os.environ["MINICORP_SERVICE_TOKEN"]
|
||||
|
||||
EVIDENCE_LOG = os.environ["MINICORP_EVIDENCE_LOG"]
|
||||
|
||||
SEQUENCE = (
|
||||
"ok",
|
||||
"ok",
|
||||
"error",
|
||||
"ok",
|
||||
"error",
|
||||
)
|
||||
|
||||
lock = threading.Lock()
|
||||
request_count = 0
|
||||
|
||||
|
||||
def next_result():
|
||||
global request_count
|
||||
|
||||
with lock:
|
||||
request_count += 1
|
||||
number = request_count
|
||||
|
||||
result = SEQUENCE[
|
||||
(number - 1) % len(SEQUENCE)
|
||||
]
|
||||
|
||||
return number, result
|
||||
|
||||
|
||||
class StatusHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path != "/":
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
number, result = next_result()
|
||||
source = self.client_address[0]
|
||||
|
||||
record_event(
|
||||
EVIDENCE_LOG,
|
||||
service="status",
|
||||
service_token=SERVICE_TOKEN,
|
||||
source=source,
|
||||
port=PORT,
|
||||
result=result,
|
||||
request=number,
|
||||
)
|
||||
|
||||
response = {
|
||||
"status": result,
|
||||
"service": "minicorp-api",
|
||||
"service_id": SERVICE_ID,
|
||||
}
|
||||
|
||||
body = (
|
||||
json.dumps(
|
||||
response,
|
||||
sort_keys=True,
|
||||
)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header(
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
)
|
||||
self.send_header(
|
||||
"Content-Length",
|
||||
str(len(body)),
|
||||
)
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
server = ThreadingHTTPServer(
|
||||
(HOST, PORT),
|
||||
StatusHandler,
|
||||
)
|
||||
|
||||
print(
|
||||
f"MiniCorp status service listening on {HOST}:{PORT}"
|
||||
)
|
||||
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from service_common import record_event
|
||||
|
||||
|
||||
HOST = os.environ["MINICORP_SERVICE_HOST"]
|
||||
PORT = int(os.environ["MINICORP_SERVICE_PORT"])
|
||||
|
||||
SERVICE_ID = os.environ["MINICORP_SERVICE_ID"]
|
||||
SERVICE_TOKEN = os.environ["MINICORP_SERVICE_TOKEN"]
|
||||
|
||||
EVIDENCE_LOG = os.environ["MINICORP_EVIDENCE_LOG"]
|
||||
|
||||
|
||||
class WebHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path != "/":
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
source = self.client_address[0]
|
||||
|
||||
record_event(
|
||||
EVIDENCE_LOG,
|
||||
service="web",
|
||||
service_token=SERVICE_TOKEN,
|
||||
source=source,
|
||||
port=PORT,
|
||||
)
|
||||
|
||||
body = (
|
||||
"MiniCorp Employee Web Portal\n"
|
||||
f"service_id: {SERVICE_ID}\n"
|
||||
"environment: production\n"
|
||||
"status: healthy\n"
|
||||
).encode("utf-8")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header(
|
||||
"Content-Type",
|
||||
"text/plain; charset=utf-8",
|
||||
)
|
||||
self.send_header(
|
||||
"Content-Length",
|
||||
str(len(body)),
|
||||
)
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
server = ThreadingHTTPServer(
|
||||
(HOST, PORT),
|
||||
WebHandler,
|
||||
)
|
||||
|
||||
print(
|
||||
f"MiniCorp web service listening on {HOST}:{PORT}"
|
||||
)
|
||||
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user