Add Proxmox ISO builder, CSRF protection, boot-files integration
- Add build-proxmox-iso.sh: remaster Ubuntu ISO with autoinstall config, offline packages, playbook, webapp, and boot files for zero-touch Proxmox VM deployment - Add boot-files/ directory for WinPE boot files (wimboot, boot.wim, BCD, ipxe.efi, etc.) sourced from WestJeff playbook - Update build-usb.sh and test-vm.sh to bundle boot-files automatically - Add usb_root variable to playbook, fix all file copy paths to use it - Unify Apache VirtualHost config (merge default site + webapp proxy) - Add CSRF token protection to all webapp POST forms and API endpoints - Update README with Proxmox deployment instructions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -11,12 +12,14 @@ from pathlib import Path
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
abort,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_file,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from lxml import etree
|
||||
@@ -71,6 +74,32 @@ FRIENDLY_NAMES = {
|
||||
"ge-shopfloor-mce": "GE Legacy Shop Floor MCE",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CSRF protection
|
||||
# ---------------------------------------------------------------------------
|
||||
def generate_csrf_token():
|
||||
"""Return the CSRF token for the current session, creating one if needed."""
|
||||
if "_csrf_token" not in session:
|
||||
session["_csrf_token"] = secrets.token_hex(32)
|
||||
return session["_csrf_token"]
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_csrf_token():
|
||||
"""Make csrf_token() available in all templates."""
|
||||
return {"csrf_token": generate_csrf_token}
|
||||
|
||||
|
||||
@app.before_request
|
||||
def validate_csrf():
|
||||
"""Reject POST requests with a missing or invalid CSRF token."""
|
||||
if request.method != "POST":
|
||||
return
|
||||
token = request.form.get("_csrf_token") or request.headers.get("X-CSRF-Token")
|
||||
if not token or token != generate_csrf_token():
|
||||
abort(403)
|
||||
|
||||
|
||||
NS = "urn:schemas-microsoft-com:unattend"
|
||||
WCM = "http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||
NSMAP = {None: NS, "wcm": WCM}
|
||||
|
||||
@@ -243,9 +243,13 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
saveRawBtn.disabled = true;
|
||||
saveRawBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Saving...';
|
||||
|
||||
var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify({ raw_xml: xmlContent })
|
||||
})
|
||||
.then(function (resp) { return resp.json(); })
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('clonezilla_upload') }}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-upload me-2"></i>Upload Backup</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
@@ -100,6 +101,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="deleteForm" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-exclamation-triangle me-2 text-danger"></i>Confirm Delete</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{% block title %}PXE Server Manager{% endblock %}</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
||||
<link href="{{ url_for('static', filename='bootstrap.min.css') }}" rel="stylesheet">
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="card-body">
|
||||
{% if usb_mounts %}
|
||||
<form method="POST" id="importForm">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-3">
|
||||
<label for="source" class="form-label fw-semibold">Source (USB Mount Point)</label>
|
||||
<select class="form-select" name="source" id="source" required>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="deleteForm" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-exclamation-triangle me-2 text-danger"></i>Confirm Delete</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<form action="{{ url_for('startnet_save') }}" method="post" id="startnetForm">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<textarea name="content" class="form-control cmd-editor" id="cmdEditor"
|
||||
spellcheck="false">{{ content }}</textarea>
|
||||
</form>
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
</ul>
|
||||
|
||||
<form method="POST" id="unattendForm">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="tab-content">
|
||||
|
||||
<!-- ==================== FORM VIEW ==================== -->
|
||||
|
||||
Reference in New Issue
Block a user