commit e2b9dc0c2f34798a2c56741093669cf9496d92f8 Author: mayank Date: Wed Mar 11 12:15:43 2026 +0530 first commit diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml new file mode 100644 index 0000000..b102247 --- /dev/null +++ b/.gitea/workflows/build-push.yaml @@ -0,0 +1,37 @@ +name: Build and Push + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to Harbor + run: | + echo '${{ secrets.HARBOR_TOKEN }}' | docker login YOUR_REGISTRY -u '${{ secrets.HARBOR_USERNAME }}' --password-stdin + + - name: Build and Push Docker image + run: | + IMAGE=YOUR_REGISTRY/${{ vars.HARBOR_PROJECT }}/phoenix:${{ gitea.sha }} + docker build -t $IMAGE . + docker push $IMAGE + docker tag $IMAGE YOUR_REGISTRY/${{ vars.HARBOR_PROJECT }}/phoenix:latest + docker push YOUR_REGISTRY/${{ vars.HARBOR_PROJECT }}/phoenix:latest + echo "Pushed: $IMAGE" + + - name: Update deployments repo + run: | + git clone https://gitea-actions:${{ secrets.DEPLOY_TOKEN }}@YOUR_GITEA/${{ gitea.repository_owner }}/deployments.git + cd deployments + sed -i "s|image: .*/phoenix:.*|image: YOUR_REGISTRY/${{ vars.HARBOR_PROJECT }}/phoenix:${{ gitea.sha }}|" phoenix/deployment.yaml + git config user.name "gitea-actions" + git config user.email "gitea-actions@localhost" + git add phoenix/deployment.yaml + git diff --cached --quiet || git commit -m "chore: update phoenix image to ${{ gitea.sha }}" + git push || (git pull --rebase && git push) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..136fbe7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.13 + +WORKDIR /app + +COPY app.py . + +RUN pip install --no-cache-dir flask + +EXPOSE 8080 + +CMD ["python", "app.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f4def0 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Phoenix App + +**WARNING: This application contains intentional security vulnerabilities. DO NOT deploy on production clusters.** + +A deliberately insecure application for Kubernetes hardening training. + +## Purpose + +The app demonstrates common Kubernetes misconfigurations and vulnerabilities, including: +- Remote Code Execution (RCE) +- Overpermissive RBAC +- Privileged containers +- Host filesystem access +- Host PID/Network namespace access + +Attendees learn to defend against these attacks through progressive infrastructure hardening. diff --git a/app.py b/app.py new file mode 100644 index 0000000..70a3ca8 --- /dev/null +++ b/app.py @@ -0,0 +1,279 @@ +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 = ''' + + + + + + Phoenix + + + + + +
+

Phoenix

+ + {% if error %} +

{{ error }}

+ {% else %} +

Phoenix is up and running for {{ uptime }}

+ +
+ Details + + + + + + +
Deployment{{ deployment }}
Status{{ status }}
Replicas{{ replicas }}
Namespace{{ namespace }}
Created{{ created }}
+
+ {% endif %} +
+ + +''' + + +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 = ''' + + + + Debug Console + + + +

Phoenix Debug Console

+

Internal use only - remove before production deployment

+
+ + +
+ {% if output %} +
$ {{ cmd }}
+{{ output }}
+ {% endif %} + + +''' + + +@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://:/{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)