fix(security): protect admin credentials and plugin downloads
Build and publish Docker image / build-and-publish (push) Successful in 19s
Build and publish Docker image / build-and-publish (push) Successful in 19s
This commit is contained in:
1 parent
84381ddc23
commit
91d7c78392
2 files changed
+42
-9
No files matched your search
+6
-1
@@ -196,6 +196,11 @@ def admin_get_user_servers(user_id):
|
|||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
servers = get_user_servers(user_id)
|
servers = get_user_servers(user_id)
|
||||||
|
# Admin server management does not need connection credentials.
|
||||||
|
servers = [
|
||||||
|
{key: value for key, value in server.items() if key != 'sftp_password'}
|
||||||
|
for server in servers
|
||||||
|
]
|
||||||
return jsonify({'success': True, 'servers': servers}), 200
|
return jsonify({'success': True, 'servers': servers}), 200
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -331,7 +336,7 @@ def admin_get_all_servers():
|
|||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT s.id, s.name, s.cpu, s.ram, s.storage, s.image, s.type, s.version, s.loader, s.java_version,
|
SELECT s.id, s.name, s.cpu, s.ram, s.storage, s.image, s.type, s.version, s.loader, s.java_version,
|
||||||
s.status, s.ports, s.created_at, u.username as owner, u.id as owner_id,
|
s.status, s.ports, s.created_at, u.username as owner, u.id as owner_id,
|
||||||
s.sftp_username, s.sftp_password, s.sftp_port
|
s.sftp_username, s.sftp_port
|
||||||
FROM servers s
|
FROM servers s
|
||||||
JOIN users u ON s.owner_id = u.id
|
JOIN users u ON s.owner_id = u.id
|
||||||
ORDER BY u.username, s.name
|
ORDER BY u.username, s.name
|
||||||
|
|||||||
+36
-8
@@ -4,8 +4,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify, request
|
||||||
import json
|
import json
|
||||||
|
from urllib.parse import urlsplit
|
||||||
from kubernetes_service import k8s_manager
|
from kubernetes_service import k8s_manager
|
||||||
import requests
|
import requests
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
from runtime_config import (
|
from runtime_config import (
|
||||||
MODRINTH_API_BASE,
|
MODRINTH_API_BASE,
|
||||||
logger,
|
logger,
|
||||||
@@ -317,6 +319,21 @@ def download_plugin_to_server():
|
|||||||
if not server_id or not plugin_url:
|
if not server_id or not plugin_url:
|
||||||
return jsonify({'error': 'Server ID and plugin URL are required'}), 400
|
return jsonify({'error': 'Server ID and plugin URL are required'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed_plugin_url = urlsplit(plugin_url)
|
||||||
|
plugin_port = parsed_plugin_url.port
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return jsonify({'error': 'Plugin URL is invalid'}), 400
|
||||||
|
if (parsed_plugin_url.scheme != 'https'
|
||||||
|
or parsed_plugin_url.hostname != 'cdn.modrinth.com'
|
||||||
|
or parsed_plugin_url.username or parsed_plugin_url.password
|
||||||
|
or plugin_port not in (None, 443)):
|
||||||
|
return jsonify({'error': 'Plugin files must come from the Modrinth CDN'}), 400
|
||||||
|
|
||||||
|
safe_file_name = secure_filename(str(file_name or 'plugin.jar'))
|
||||||
|
if not safe_file_name.lower().endswith('.jar'):
|
||||||
|
return jsonify({'error': 'Plugin file name must end in .jar'}), 400
|
||||||
|
|
||||||
# Verify server belongs to user
|
# Verify server belongs to user
|
||||||
user_servers = get_user_servers(user['id'])
|
user_servers = get_user_servers(user['id'])
|
||||||
server = next((s for s in user_servers if s['id'] == server_id), None)
|
server = next((s for s in user_servers if s['id'] == server_id), None)
|
||||||
@@ -330,26 +347,37 @@ def download_plugin_to_server():
|
|||||||
|
|
||||||
# Download plugin file
|
# Download plugin file
|
||||||
logger.info(f"Downloading plugin from {plugin_url} for server {server_id}")
|
logger.info(f"Downloading plugin from {plugin_url} for server {server_id}")
|
||||||
plugin_response = requests.get(plugin_url, stream=True, timeout=30)
|
plugin_response = requests.get(
|
||||||
|
plugin_url, stream=True, timeout=30, allow_redirects=False
|
||||||
|
)
|
||||||
|
|
||||||
if plugin_response.status_code != 200:
|
if plugin_response.status_code != 200:
|
||||||
|
plugin_response.close()
|
||||||
return jsonify({'error': f'Failed to download plugin: HTTP {plugin_response.status_code}'}), 500
|
return jsonify({'error': f'Failed to download plugin: HTTP {plugin_response.status_code}'}), 500
|
||||||
|
|
||||||
# Determine plugins directory based on server type
|
# Determine plugins directory based on server type
|
||||||
# For Minecraft servers, plugins go in /data/plugins directory
|
# For Minecraft servers, plugins go in /data/plugins directory
|
||||||
plugins_dir = '/data/plugins'
|
plugins_dir = '/data/plugins'
|
||||||
file_path = f'{plugins_dir}/{file_name}'
|
file_path = f'{plugins_dir}/{safe_file_name}'
|
||||||
|
|
||||||
# Save plugin to temporary file first
|
# Save plugin to temporary file first
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.jar') as temp_file:
|
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jar')
|
||||||
temp_file_path = temp_file.name
|
temp_file_path = temp_file.name
|
||||||
for chunk in plugin_response.iter_content(chunk_size=8192):
|
|
||||||
if chunk:
|
|
||||||
temp_file.write(chunk)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
with temp_file:
|
||||||
|
try:
|
||||||
|
total_bytes = 0
|
||||||
|
for chunk in plugin_response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
total_bytes += len(chunk)
|
||||||
|
if total_bytes > 100 * 1024 * 1024:
|
||||||
|
raise ValueError('Plugin file exceeds the 100 MB limit')
|
||||||
|
temp_file.write(chunk)
|
||||||
|
finally:
|
||||||
|
plugin_response.close()
|
||||||
|
|
||||||
# Upload to server
|
# Upload to server
|
||||||
namespace = f"user-{user['username']}".lower()
|
namespace = f"user-{user['username']}".lower()
|
||||||
pods = k8s_manager.get_pods_for_server(namespace, server_id)
|
pods = k8s_manager.get_pods_for_server(namespace, server_id)
|
||||||
|
|||||||
Reference in new issue
Block a user