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
|
||||
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
@@ -331,7 +336,7 @@ def admin_get_all_servers():
|
||||
cursor.execute('''
|
||||
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.sftp_username, s.sftp_password, s.sftp_port
|
||||
s.sftp_username, s.sftp_port
|
||||
FROM servers s
|
||||
JOIN users u ON s.owner_id = u.id
|
||||
ORDER BY u.username, s.name
|
||||
|
||||
+36
-8
@@ -4,8 +4,10 @@
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, jsonify, request
|
||||
import json
|
||||
from urllib.parse import urlsplit
|
||||
from kubernetes_service import k8s_manager
|
||||
import requests
|
||||
from werkzeug.utils import secure_filename
|
||||
from runtime_config import (
|
||||
MODRINTH_API_BASE,
|
||||
logger,
|
||||
@@ -317,6 +319,21 @@ def download_plugin_to_server():
|
||||
if not server_id or not plugin_url:
|
||||
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
|
||||
user_servers = get_user_servers(user['id'])
|
||||
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
|
||||
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:
|
||||
plugin_response.close()
|
||||
return jsonify({'error': f'Failed to download plugin: HTTP {plugin_response.status_code}'}), 500
|
||||
|
||||
# Determine plugins directory based on server type
|
||||
# For Minecraft servers, plugins go in /data/plugins directory
|
||||
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
|
||||
import tempfile
|
||||
import os
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.jar') as temp_file:
|
||||
temp_file_path = temp_file.name
|
||||
for chunk in plugin_response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
temp_file.write(chunk)
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jar')
|
||||
temp_file_path = temp_file.name
|
||||
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
|
||||
namespace = f"user-{user['username']}".lower()
|
||||
pods = k8s_manager.get_pods_for_server(namespace, server_id)
|
||||
|
||||
Reference in new issue
Block a user