mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-06 12:55:44 +02:00
Add pull image functionality and enhance error handling
- Implemented a new `pullImage` method in `ContainerManager` to pull Docker images with validation and error handling. - Added a corresponding URL route for the `pullImage` view. - Updated the `views.py` to handle user permissions and session management for the new feature. - Improved error handling across the codebase by replacing `BaseException` with `Exception`. - Enhanced rate limiting logic to support JSON format for tracking timestamps. - Updated UI styles in `manageImages.html` for consistency in gradient backgrounds.
This commit is contained in:
@@ -14,6 +14,7 @@ import json
|
||||
from plogical.acl import ACLManager
|
||||
import plogical.CyberCPLogFileWriter as logging
|
||||
from django.shortcuts import HttpResponse, render, redirect
|
||||
from django.urls import reverse
|
||||
from loginSystem.models import Administrator
|
||||
import subprocess
|
||||
import shlex
|
||||
@@ -50,7 +51,7 @@ class ContainerManager(multi.Thread):
|
||||
elif self.function == 'restartGunicorn':
|
||||
command = 'sudo systemctl restart gunicorn.socket'
|
||||
ProcessUtilities.executioner(command)
|
||||
except BaseException as msg:
|
||||
except Exception as msg:
|
||||
logging.CyberCPLogFileWriter.writeToFile( str(msg) + ' [ContainerManager.run]')
|
||||
|
||||
@staticmethod
|
||||
@@ -61,7 +62,7 @@ class ContainerManager(multi.Thread):
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
except BaseException as msg:
|
||||
except Exception as msg:
|
||||
logging.CyberCPLogFileWriter.writeToFile(str(msg))
|
||||
return 0
|
||||
|
||||
@@ -80,7 +81,7 @@ class ContainerManager(multi.Thread):
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
except BaseException as msg:
|
||||
except Exception as msg:
|
||||
logging.CyberCPLogFileWriter.statusWriter(ServerStatusUtil.lswsInstallStatusPath, str(msg) + ' [404].', 1)
|
||||
|
||||
def createContainer(self, request=None, userID=None, data=None):
|
||||
@@ -124,7 +125,7 @@ class ContainerManager(multi.Thread):
|
||||
portConfig[portDef[0]] = portDef[1]
|
||||
|
||||
if image is None or image is '' or tag is None or tag is '':
|
||||
return redirect(loadImages)
|
||||
return redirect(reverse('containerImage'))
|
||||
|
||||
Data = {"ownerList": adminNames, "image": image, "name": name, "tag": tag, "portConfig": portConfig,
|
||||
"envList": envList}
|
||||
@@ -374,7 +375,7 @@ class ContainerManager(multi.Thread):
|
||||
return HttpResponse(json_data)
|
||||
|
||||
|
||||
except BaseException as msg:
|
||||
except Exception as msg:
|
||||
data_ret = {'createContainerStatus': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
@@ -413,11 +414,77 @@ class ContainerManager(multi.Thread):
|
||||
return HttpResponse(json_data)
|
||||
|
||||
|
||||
except BaseException as msg:
|
||||
except Exception as msg:
|
||||
data_ret = {'installImageStatus': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def pullImage(self, userID=None, data=None):
|
||||
"""
|
||||
Pull a Docker image from registry with proper error handling and security checks
|
||||
"""
|
||||
try:
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
if admin.acl.adminStatus != 1:
|
||||
return ACLManager.loadErrorJson('pullImageStatus', 0)
|
||||
|
||||
client = docker.from_env()
|
||||
dockerAPI = docker.APIClient()
|
||||
|
||||
image = data['image']
|
||||
tag = data.get('tag', 'latest')
|
||||
|
||||
# Validate image name to prevent injection
|
||||
if not self._validate_image_name(image):
|
||||
data_ret = {'pullImageStatus': 0, 'error_message': 'Invalid image name format'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Check if image already exists
|
||||
try:
|
||||
inspectImage = dockerAPI.inspect_image(image + ":" + tag)
|
||||
data_ret = {'pullImageStatus': 0, 'error_message': "Image already exists locally"}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except docker.errors.ImageNotFound:
|
||||
pass
|
||||
|
||||
# Pull the image
|
||||
try:
|
||||
pulled_image = client.images.pull(image, tag=tag)
|
||||
data_ret = {
|
||||
'pullImageStatus': 1,
|
||||
'error_message': "None",
|
||||
'image_id': pulled_image.id,
|
||||
'image_name': image,
|
||||
'tag': tag
|
||||
}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except docker.errors.APIError as err:
|
||||
data_ret = {'pullImageStatus': 0, 'error_message': f'Docker API error: {str(err)}'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except docker.errors.ImageNotFound as err:
|
||||
data_ret = {'pullImageStatus': 0, 'error_message': f'Image not found: {str(err)}'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except Exception as msg:
|
||||
data_ret = {'pullImageStatus': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def _validate_image_name(self, image_name):
|
||||
"""Validate Docker image name to prevent injection attacks"""
|
||||
if not image_name or len(image_name) > 255:
|
||||
return False
|
||||
|
||||
# Allow alphanumeric, hyphens, underscores, dots, and forward slashes
|
||||
import re
|
||||
pattern = r'^[a-zA-Z0-9._/-]+$'
|
||||
return re.match(pattern, image_name) is not None
|
||||
|
||||
def submitContainerDeletion(self, userID=None, data=None, called=False):
|
||||
try:
|
||||
name = data['name']
|
||||
@@ -1268,7 +1335,7 @@ class ContainerManager(multi.Thread):
|
||||
data['JobID'] = ''
|
||||
data['Domain'] = dockersite.admin.domain
|
||||
data['domain'] = dockersite.admin.domain
|
||||
data['WPemal'] = WPemail
|
||||
data['WPemail'] = WPemail
|
||||
data['Owner'] = dockersite.admin.admin.userName
|
||||
data['userID'] = userID
|
||||
data['MysqlCPU'] = dockersite.CPUsMySQL
|
||||
@@ -1576,18 +1643,19 @@ class ContainerManager(multi.Thread):
|
||||
return {'valid': True, 'reason': 'Command passed validation'}
|
||||
|
||||
def _check_rate_limit(self, userID, containerName):
|
||||
"""Simple rate limiting: max 10 commands per minute per user-container pair"""
|
||||
"""Enhanced rate limiting: max 10 commands per minute per user-container pair"""
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
|
||||
# Create rate limit tracking directory
|
||||
rate_limit_dir = '/tmp/cyberpanel_docker_rate_limit'
|
||||
if not os.path.exists(rate_limit_dir):
|
||||
try:
|
||||
os.makedirs(rate_limit_dir, mode=0o755)
|
||||
except:
|
||||
except Exception as e:
|
||||
# If we can't create rate limit tracking, allow the command but log it
|
||||
logging.CyberCPLogFileWriter.writeToFile('Warning: Could not create rate limit directory')
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not create rate limit directory: {str(e)}')
|
||||
return True
|
||||
|
||||
# Rate limit file per user-container
|
||||
@@ -1599,22 +1667,33 @@ class ContainerManager(multi.Thread):
|
||||
timestamps = []
|
||||
if os.path.exists(rate_file):
|
||||
with open(rate_file, 'r') as f:
|
||||
timestamps = [float(line.strip()) for line in f if line.strip()]
|
||||
try:
|
||||
data = json.load(f)
|
||||
timestamps = data.get('timestamps', [])
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
# Fallback to old format
|
||||
f.seek(0)
|
||||
timestamps = [float(line.strip()) for line in f if line.strip()]
|
||||
|
||||
# Remove timestamps older than 1 minute
|
||||
recent_timestamps = [ts for ts in timestamps if current_time - ts < 60]
|
||||
|
||||
# Check if limit exceeded
|
||||
if len(recent_timestamps) >= 10:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Rate limit exceeded for user {userID}, container {containerName}')
|
||||
return False
|
||||
|
||||
# Add current timestamp
|
||||
recent_timestamps.append(current_time)
|
||||
|
||||
# Write back to file
|
||||
# Write back to file with JSON format
|
||||
with open(rate_file, 'w') as f:
|
||||
for ts in recent_timestamps:
|
||||
f.write(f'{ts}\n')
|
||||
json.dump({
|
||||
'timestamps': recent_timestamps,
|
||||
'last_updated': current_time,
|
||||
'user_id': userID,
|
||||
'container_name': containerName
|
||||
}, f)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user