phoenix/app.py
2026-03-11 12:15:43 +05:30

280 lines
8.3 KiB
Python

from flask import Flask, request, render_template_string
import subprocess
import os
import urllib.request
import urllib.error
import ssl
import json
from datetime import datetime, timezone
app = Flask(__name__)
DEBUG_PATH = os.environ.get('DEBUG_PATH')
DEPLOYMENT_NAME = os.environ.get('DEPLOYMENT_NAME', 'phoenix-app')
DASHBOARD_HTML = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Phoenix</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'IBM Plex Sans', -apple-system, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
color: #1a1a1a;
}
.container {
text-align: center;
padding: 3rem 2rem;
}
h1 {
font-size: 2.5rem;
font-weight: 600;
letter-spacing: -0.03em;
color: #111;
margin-bottom: 1.5rem;
}
.status-line {
font-size: 1.125rem;
color: #444;
line-height: 1.6;
}
.status-line strong {
color: #16a34a;
font-weight: 600;
}
.error-text {
color: #dc2626;
}
details {
margin-top: 2rem;
text-align: left;
display: inline-block;
}
summary {
cursor: pointer;
font-size: 0.875rem;
color: #666;
padding: 0.5rem 0;
user-select: none;
}
summary:hover {
color: #111;
}
summary::marker {
color: #999;
}
.details-table {
margin-top: 1rem;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8125rem;
border-collapse: collapse;
}
.details-table td {
padding: 0.375rem 0;
}
.details-table td:first-child {
color: #888;
padding-right: 1.5rem;
}
.details-table td:last-child {
color: #333;
}
.status-running {
color: #16a34a;
}
</style>
</head>
<body>
<div class="container">
<h1>Phoenix</h1>
{% if error %}
<p class="status-line error-text"><strong>{{ error }}</strong></p>
{% else %}
<p class="status-line">Phoenix is up and running for <strong>{{ uptime }}</strong></p>
<details {% if details_open %}open{% endif %}>
<summary>Details</summary>
<table class="details-table">
<tr><td>Deployment</td><td>{{ deployment }}</td></tr>
<tr><td>Status</td><td class="status-running">{{ status }}</td></tr>
<tr><td>Replicas</td><td>{{ replicas }}</td></tr>
<tr><td>Namespace</td><td>{{ namespace }}</td></tr>
<tr><td>Created</td><td>{{ created }}</td></tr>
</table>
</details>
{% endif %}
</div>
</body>
</html>
'''
def get_deployment_info():
"""Fetch deployment info from K8s API using mounted ServiceAccount token."""
try:
with open('/var/run/secrets/kubernetes.io/serviceaccount/token') as f:
token = f.read().strip()
with open('/var/run/secrets/kubernetes.io/serviceaccount/namespace') as f:
namespace = f.read().strip()
ctx = ssl.create_default_context()
ctx.load_verify_locations('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt')
api_url = f'https://kubernetes.default.svc/apis/apps/v1/namespaces/{namespace}/deployments/{DEPLOYMENT_NAME}'
req = urllib.request.Request(api_url, headers={'Authorization': f'Bearer {token}'})
with urllib.request.urlopen(req, context=ctx, timeout=5) as resp:
deploy = json.loads(resp.read())
created_str = deploy['metadata']['creationTimestamp']
created_dt = datetime.fromisoformat(created_str.replace('Z', '+00:00'))
now = datetime.now(timezone.utc)
uptime_delta = now - created_dt
days = uptime_delta.days
hours, remainder = divmod(uptime_delta.seconds, 3600)
minutes, _ = divmod(remainder, 60)
if days > 0:
uptime = f"{days}d {hours}h {minutes}m"
elif hours > 0:
uptime = f"{hours}h {minutes}m"
else:
uptime = f"{minutes}m"
ready = deploy.get('status', {}).get('readyReplicas', 0)
desired = deploy.get('spec', {}).get('replicas', 1)
status = 'Running' if ready == desired else 'Degraded'
return {
'deployment': DEPLOYMENT_NAME,
'namespace': namespace,
'status': status,
'replicas': f"{ready}/{desired}",
'created': created_dt.strftime('%Y-%m-%d %H:%M:%S UTC'),
'uptime': uptime,
'error': None
}
except FileNotFoundError:
return {'error': 'Not running in Kubernetes'}
except urllib.error.HTTPError as e:
if e.code == 403:
return {'error': 'Unable to fetch deployment details (forbidden)'}
return {'error': f'Unable to fetch deployment details ({e.code})'}
except Exception as e:
return {'error': str(e)}
HTML = '''
<!DOCTYPE html>
<html>
<head>
<title>Debug Console</title>
<style>
body { font-family: monospace; background: #1a1a1a; color: #0f0; padding: 20px; }
input { width: 80%; padding: 10px; font-size: 16px; background: #000; color: #0f0; border: 1px solid #0f0; }
button { padding: 10px 20px; background: #0f0; color: #000; border: none; cursor: pointer; }
pre { background: #000; padding: 15px; border: 1px solid #333; overflow-x: auto; white-space: pre-wrap; }
h1 { color: #0f0; }
.warning { color: #f90; font-size: 12px; }
</style>
</head>
<body>
<h1>Phoenix Debug Console</h1>
<p class="warning">Internal use only - remove before production deployment</p>
<form method="POST">
<input type="text" name="cmd" placeholder="Enter command..." value="{{ cmd }}" autofocus>
<button type="submit">Run</button>
</form>
{% if output %}
<pre>$ {{ cmd }}
{{ output }}</pre>
{% endif %}
</body>
</html>
'''
@app.route('/health')
def health():
open('/tmp/phoenix_heartbeat', 'w').write('1')
return {'status': 'healthy'}
@app.route('/')
def root():
if request.args.get('dummy') == 'true':
info = {
'deployment': 'phoenix-app',
'namespace': 'web',
'status': 'Running',
'replicas': '1/1',
'created': '2026-02-21 10:32:15 UTC',
'uptime': '2d 5h 23m',
'error': None,
'details_open': True
}
else:
info = get_deployment_info()
info['details_open'] = False
return render_template_string(DASHBOARD_HTML, **info)
if DEBUG_PATH:
print(f"\n{'='*60}")
print(f"DEBUG ENDPOINT ENABLED")
print(f"DEBUG PATH: /{DEBUG_PATH}/")
print(f"Access URL: http://<host>:<port>/{DEBUG_PATH}/")
print(f"{'='*60}\n", flush=True)
@app.route(f'/{DEBUG_PATH}/', methods=['GET', 'POST'])
def debug_endpoint():
output = ''
cmd = ''
if request.method == 'POST':
cmd = request.form.get('cmd', '')
# VULNERABLE: Direct command execution
# This is INTENTIONAL for security training purposes
try:
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=30)
output = output.decode('utf-8')
except subprocess.CalledProcessError as e:
output = e.output.decode('utf-8')
except Exception as e:
output = str(e)
return render_template_string(HTML, output=output, cmd=cmd)
else:
print("\n" + "="*60)
print("DEBUG_PATH not set - debug endpoint DISABLED (secure mode)")
print("="*60 + "\n", flush=True)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)