diff --git a/baseTemplate/static/baseTemplate/custom-js/system-status.js b/baseTemplate/static/baseTemplate/custom-js/system-status.js index 3d5c7bcd6..8b1d54cd9 100644 --- a/baseTemplate/static/baseTemplate/custom-js/system-status.js +++ b/baseTemplate/static/baseTemplate/custom-js/system-status.js @@ -1008,8 +1008,11 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { if (!$scope.blockingIP) { $scope.blockingIP = ipAddress; + // Use the new Banned IPs system instead of the old blockIPAddress var data = { - ip_address: ipAddress + ip: ipAddress, + reason: 'Brute force attack detected from SSH Security Analysis', + duration: 'permanent' }; var config = { @@ -1018,7 +1021,7 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { } }; - $http.post('/base/blockIPAddress', data, config).then(function (response) { + $http.post('/firewall/addBannedIP', data, config).then(function (response) { $scope.blockingIP = null; if (response.data && response.data.status === 1) { // Mark IP as blocked @@ -1026,8 +1029,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { // Show success notification new PNotify({ - title: 'Success', - text: `IP address ${ipAddress} has been blocked successfully using ${response.data.firewall.toUpperCase()}`, + title: 'IP Address Banned', + text: `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`, type: 'success', delay: 5000 }); diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index 5523d03d3..53d11204c 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -234,6 +234,7 @@ width: 100%; border-collapse: collapse; margin-top: 15px; + table-layout: fixed; } .activity-table th { @@ -535,12 +536,12 @@
| USER | -IP | -COUNTRY | -DATE | -SESSION | -ACTIVITY | +USER | +IP | +COUNTRY | +DATE | +SESSION | +ACTIVITY | {$ login.date $} | {$ login.session $} |
-
+
+
+
+
|
@@ -561,12 +570,12 @@
|---|
| USER | -IP | -COUNTRY | -DATE | -SESSION | -ACTIVITY | +USER | +IP | +COUNTRY | +DATE | +SESSION | +ACTIVITY | Wed Jun 4 20:59 | Still Logged In |
-
+
+
+
+
|
@@ -672,9 +686,12 @@
onmouseout="this.style.background='#dc2626'">
- Block IP
- Blocking...
+ Ban IP Permanently
+ Banning...
+
+ Manage in Firewall
+
Blocked
diff --git a/dockerManager/container.py b/dockerManager/container.py
index 6d98882c8..275891abf 100644
--- a/dockerManager/container.py
+++ b/dockerManager/container.py
@@ -2095,4 +2095,211 @@ class ContainerManager(multi.Thread):
logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.deleteContainerKeepData]')
data_ret = {'deleteContainerKeepDataStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ def updateContainer(self, userID=None, data=None):
+ """
+ Update container with new image while preserving data using Docker volumes
+ This function handles the complete container update process:
+ 1. Stops the current container
+ 2. Creates a backup of the container configuration
+ 3. Removes the old container
+ 4. Pulls the new image
+ 5. Creates a new container with the same configuration but new image
+ 6. Preserves all volumes and data
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ client = docker.from_env()
+ dockerAPI = docker.APIClient()
+
+ containerName = data['containerName']
+ newImage = data['newImage']
+ newTag = data.get('newTag', 'latest')
+
+ # Get the current container
+ try:
+ currentContainer = client.containers.get(containerName)
+ except docker.errors.NotFound:
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Container {containerName} not found'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+ except Exception as e:
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Error accessing container: {str(e)}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ # Get container configuration for recreation
+ containerConfig = currentContainer.attrs['Config']
+ hostConfig = currentContainer.attrs['HostConfig']
+
+ # Extract volumes for data preservation
+ volumes = {}
+ if 'Binds' in hostConfig and hostConfig['Binds']:
+ for bind in hostConfig['Binds']:
+ if ':' in bind:
+ parts = bind.split(':')
+ if len(parts) >= 2:
+ host_path = parts[0]
+ container_path = parts[1]
+ mode = parts[2] if len(parts) > 2 else 'rw'
+ volumes[host_path] = {'bind': container_path, 'mode': mode}
+
+ # Extract environment variables
+ environment = containerConfig.get('Env', [])
+ envDict = {}
+ for env in environment:
+ if '=' in env:
+ key, value = env.split('=', 1)
+ envDict[key] = value
+
+ # Extract port mappings
+ portConfig = {}
+ if 'PortBindings' in hostConfig and hostConfig['PortBindings']:
+ for container_port, host_bindings in hostConfig['PortBindings'].items():
+ if host_bindings and len(host_bindings) > 0:
+ host_port = host_bindings[0]['HostPort']
+ portConfig[container_port] = host_port
+
+ # Extract memory limit
+ memory_limit = hostConfig.get('Memory', 0)
+ if memory_limit > 0:
+ memory_limit = memory_limit // 1048576 # Convert bytes to MB
+
+ # Stop the current container
+ try:
+ if currentContainer.status == 'running':
+ currentContainer.stop(timeout=30)
+ logging.CyberCPLogFileWriter.writeToFile(f'Stopped container {containerName} for update')
+ except Exception as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Error stopping container {containerName}: {str(e)}')
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Error stopping container: {str(e)}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ # Remove the old container
+ try:
+ currentContainer.remove(force=True)
+ logging.CyberCPLogFileWriter.writeToFile(f'Removed old container {containerName}')
+ except Exception as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Error removing old container {containerName}: {str(e)}')
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Error removing old container: {str(e)}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ # Pull the new image
+ try:
+ image_name = f"{newImage}:{newTag}"
+ logging.CyberCPLogFileWriter.writeToFile(f'Pulling new image {image_name}')
+ client.images.pull(newImage, tag=newTag)
+ logging.CyberCPLogFileWriter.writeToFile(f'Successfully pulled image {image_name}')
+ except Exception as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Error pulling image {newImage}:{newTag}: {str(e)}')
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Error pulling new image: {str(e)}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ # Create new container with same configuration but new image
+ try:
+ containerArgs = {
+ 'image': image_name,
+ 'detach': True,
+ 'name': containerName,
+ 'ports': portConfig,
+ 'publish_all_ports': True,
+ 'environment': envDict,
+ 'volumes': volumes
+ }
+
+ if memory_limit > 0:
+ containerArgs['mem_limit'] = memory_limit * 1048576
+
+ newContainer = client.containers.create(**containerArgs)
+ logging.CyberCPLogFileWriter.writeToFile(f'Created new container {containerName} with image {image_name}')
+
+ # Start the new container
+ newContainer.start()
+ logging.CyberCPLogFileWriter.writeToFile(f'Started updated container {containerName}')
+
+ except docker.errors.APIError as err:
+ error_message = str(err)
+ if "port is already allocated" in error_message:
+ try:
+ newContainer.remove(force=True)
+ except:
+ pass
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Docker API error creating container: {error_message}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+ except docker.errors.ImageNotFound as err:
+ error_message = str(err)
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'New image not found: {error_message}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+ except Exception as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Error creating new container {containerName}: {str(e)}')
+ data_ret = {'updateContainerStatus': 0, 'error_message': f'Error creating new container: {str(e)}'}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ # Log successful update
+ logging.CyberCPLogFileWriter.writeToFile(f'Successfully updated container {containerName} to image {image_name}')
+
+ data_ret = {
+ 'updateContainerStatus': 1,
+ 'error_message': 'None',
+ 'message': f'Container {containerName} successfully updated to {image_name}'
+ }
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ except Exception as msg:
+ logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.updateContainer]')
+ data_ret = {'updateContainerStatus': 0, 'error_message': str(msg)}
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ def listContainers(self, userID=None):
+ """
+ Get list of all Docker containers
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ client = docker.from_env()
+
+ # Get all containers (including stopped ones)
+ containers = client.containers.list(all=True)
+
+ container_list = []
+ for container in containers:
+ container_info = {
+ 'name': container.name,
+ 'image': container.image.tags[0] if container.image.tags else container.image.short_id,
+ 'status': container.status,
+ 'state': container.attrs['State']['Status'],
+ 'created': container.attrs['Created'],
+ 'ports': container.attrs['NetworkSettings']['Ports'],
+ 'mounts': container.attrs['Mounts'],
+ 'id': container.short_id
+ }
+ container_list.append(container_info)
+
+ data_ret = {
+ 'status': 1,
+ 'error_message': 'None',
+ 'containers': container_list
+ }
+ json_data = json.dumps(data_ret)
+ return HttpResponse(json_data)
+
+ except Exception as msg:
+ logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [ContainerManager.listContainers]')
+ data_ret = {'status': 0, 'error_message': str(msg)}
+ json_data = json.dumps(data_ret)
return HttpResponse(json_data)
\ No newline at end of file
diff --git a/dockerManager/static/dockerManager/dockerManager.js b/dockerManager/static/dockerManager/dockerManager.js
index e049906dd..d2ab7a9e3 100644
--- a/dockerManager/static/dockerManager/dockerManager.js
+++ b/dockerManager/static/dockerManager/dockerManager.js
@@ -2495,4 +2495,259 @@ app.controller('manageImages', function ($scope, $http) {
})
}
+});
+
+// Container List Controller
+app.controller('listContainers', function ($scope, $http, $timeout, $window) {
+ $scope.containers = [];
+ $scope.loading = false;
+ $scope.updateContainerName = '';
+ $scope.currentImage = '';
+ $scope.newImage = '';
+ $scope.newTag = 'latest';
+
+ // Load containers list
+ $scope.loadContainers = function() {
+ $scope.loading = true;
+ var url = '/docker/listContainers';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, {}, config).then(function(response) {
+ $scope.loading = false;
+ if (response.data.status === 1) {
+ $scope.containers = response.data.containers || [];
+ } else {
+ new PNotify({
+ title: 'Error Loading Containers',
+ text: response.data.error_message || 'Failed to load containers',
+ type: 'error'
+ });
+ }
+ }, function(error) {
+ $scope.loading = false;
+ new PNotify({
+ title: 'Connection Error',
+ text: 'Could not connect to server',
+ type: 'error'
+ });
+ });
+ };
+
+ // Initialize containers on page load
+ $scope.loadContainers();
+
+ // Open update container modal
+ $scope.openUpdateModal = function(container) {
+ $scope.updateContainerName = container.name;
+ $scope.currentImage = container.image;
+ $scope.newImage = '';
+ $scope.newTag = 'latest';
+ $('#updateContainer').modal('show');
+ };
+
+ // Perform container update
+ $scope.performUpdate = function() {
+ if (!$scope.newImage && !$scope.newTag) {
+ new PNotify({
+ title: 'Missing Information',
+ text: 'Please enter a new image name or tag',
+ type: 'error'
+ });
+ return;
+ }
+
+ // If no new image specified, use current image with new tag
+ var imageToUse = $scope.newImage || $scope.currentImage.split(':')[0];
+ var tagToUse = $scope.newTag || 'latest';
+
+ var data = {
+ containerName: $scope.updateContainerName,
+ newImage: imageToUse,
+ newTag: tagToUse
+ };
+
+ var url = '/docker/updateContainer';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ // Show loading
+ $('#updateContainer').modal('hide');
+ new PNotify({
+ title: 'Updating Container',
+ text: 'Please wait while the container is being updated...',
+ type: 'info',
+ hide: false
+ });
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.updateContainerStatus === 1) {
+ new PNotify({
+ title: 'Container Updated Successfully',
+ text: response.data.message || 'Container has been updated successfully',
+ type: 'success'
+ });
+ // Reload containers list
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Update Failed',
+ text: response.data.error_message || 'Failed to update container',
+ type: 'error'
+ });
+ }
+ }, function(error) {
+ new PNotify({
+ title: 'Update Failed',
+ text: 'Could not connect to server',
+ type: 'error'
+ });
+ });
+ };
+
+ // Container actions
+ $scope.startContainer = function(containerName) {
+ var data = { containerName: containerName };
+ var url = '/docker/startContainer';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.startContainerStatus === 1) {
+ new PNotify({
+ title: 'Container Started',
+ text: 'Container has been started successfully',
+ type: 'success'
+ });
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Start Failed',
+ text: response.data.error_message || 'Failed to start container',
+ type: 'error'
+ });
+ }
+ });
+ };
+
+ $scope.stopContainer = function(containerName) {
+ var data = { containerName: containerName };
+ var url = '/docker/stopContainer';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.stopContainerStatus === 1) {
+ new PNotify({
+ title: 'Container Stopped',
+ text: 'Container has been stopped successfully',
+ type: 'success'
+ });
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Stop Failed',
+ text: response.data.error_message || 'Failed to stop container',
+ type: 'error'
+ });
+ }
+ });
+ };
+
+ $scope.restartContainer = function(containerName) {
+ var data = { containerName: containerName };
+ var url = '/docker/restartContainer';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.restartContainerStatus === 1) {
+ new PNotify({
+ title: 'Container Restarted',
+ text: 'Container has been restarted successfully',
+ type: 'success'
+ });
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Restart Failed',
+ text: response.data.error_message || 'Failed to restart container',
+ type: 'error'
+ });
+ }
+ });
+ };
+
+ $scope.deleteContainerWithData = function(containerName) {
+ if (confirm('Are you sure you want to delete this container and all its data? This action cannot be undone.')) {
+ var data = { containerName: containerName };
+ var url = '/docker/deleteContainerWithData';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.deleteContainerWithDataStatus === 1) {
+ new PNotify({
+ title: 'Container Deleted',
+ text: 'Container and all data have been deleted successfully',
+ type: 'success'
+ });
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Delete Failed',
+ text: response.data.error_message || 'Failed to delete container',
+ type: 'error'
+ });
+ }
+ });
+ }
+ };
+
+ $scope.deleteContainerKeepData = function(containerName) {
+ if (confirm('Are you sure you want to delete this container but keep the data? The container will be removed but volumes will be preserved.')) {
+ var data = { containerName: containerName };
+ var url = '/docker/deleteContainerKeepData';
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ if (response.data.deleteContainerKeepDataStatus === 1) {
+ new PNotify({
+ title: 'Container Deleted',
+ text: 'Container has been deleted but data has been preserved',
+ type: 'success'
+ });
+ $scope.loadContainers();
+ } else {
+ new PNotify({
+ title: 'Delete Failed',
+ text: response.data.error_message || 'Failed to delete container',
+ type: 'error'
+ });
+ }
+ });
+ }
+ };
});
\ No newline at end of file
diff --git a/dockerManager/urls.py b/dockerManager/urls.py
index f4746f342..52e79295c 100644
--- a/dockerManager/urls.py
+++ b/dockerManager/urls.py
@@ -28,6 +28,7 @@ urlpatterns = [
re_path(r'^removeImage$', views.removeImage, name='removeImage'),
re_path(r'^pullImage$', views.pullImage, name='pullImage'),
re_path(r'^updateContainer$', views.updateContainer, name='updateContainer'),
+ re_path(r'^listContainers$', views.listContainers, name='listContainers'),
re_path(r'^deleteContainerWithData$', views.deleteContainerWithData, name='deleteContainerWithData'),
re_path(r'^deleteContainerKeepData$', views.deleteContainerKeepData, name='deleteContainerKeepData'),
re_path(r'^recreateContainer$', views.recreateContainer, name='recreateContainer'),
diff --git a/dockerManager/views.py b/dockerManager/views.py
index 28069bf0b..3324485bf 100644
--- a/dockerManager/views.py
+++ b/dockerManager/views.py
@@ -743,5 +743,26 @@ def getContainerEnv(request):
'success': 0,
'message': str(e)
}), content_type='application/json')
+ except KeyError:
+ return redirect(loadLoginPage)
+
+@preDockerRun
+def listContainers(request):
+ """
+ Get list of all Docker containers
+ """
+ try:
+ userID = request.session['userID']
+ currentACL = ACLManager.loadedACL(userID)
+
+ if currentACL['admin'] == 1:
+ pass
+ else:
+ return ACLManager.loadErrorJson()
+
+ cm = ContainerManager()
+ coreResult = cm.listContainers(userID)
+
+ return coreResult
except KeyError:
return redirect(loadLoginPage)
\ No newline at end of file
diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py
index 5375343f8..e0d93c922 100644
--- a/firewall/firewallManager.py
+++ b/firewall/firewallManager.py
@@ -1755,6 +1755,267 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
+ def getBannedIPs(self, userID=None):
+ """
+ Get list of banned IP addresses
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ # For now, we'll use a simple file-based storage
+ # In production, you might want to use a database
+ banned_ips_file = '/etc/cyberpanel/banned_ips.json'
+
+ banned_ips = []
+ if os.path.exists(banned_ips_file):
+ try:
+ with open(banned_ips_file, 'r') as f:
+ banned_ips = json.load(f)
+ except:
+ banned_ips = []
+
+ # Filter out expired bans
+ current_time = time.time()
+ active_banned_ips = []
+
+ for banned_ip in banned_ips:
+ if banned_ip.get('expires') == 'Never' or banned_ip.get('expires', 0) > current_time:
+ banned_ip['active'] = True
+ if banned_ip.get('expires') != 'Never':
+ banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip['expires']))
+ else:
+ banned_ip['expires'] = 'Never'
+ banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
+ else:
+ banned_ip['active'] = False
+ banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('expires', current_time)))
+ banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
+
+ active_banned_ips.append(banned_ip)
+
+ final_dic = {'status': 1, 'bannedIPs': active_banned_ips}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ except BaseException as msg:
+ final_dic = {'status': 0, 'error_message': str(msg)}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ def addBannedIP(self, userID=None, data=None):
+ """
+ Add a banned IP address
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ ip = data.get('ip', '').strip()
+ reason = data.get('reason', '').strip()
+ duration = data.get('duration', '24h')
+
+ if not ip or not reason:
+ final_dic = {'status': 0, 'error_message': 'IP address and reason are required'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ # Validate IP address format
+ import ipaddress
+ try:
+ ipaddress.ip_address(ip.split('/')[0]) # Handle CIDR notation
+ except ValueError:
+ final_dic = {'status': 0, 'error_message': 'Invalid IP address format'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ # Calculate expiration time
+ current_time = time.time()
+ if duration == 'permanent':
+ expires = 'Never'
+ else:
+ duration_map = {
+ '1h': 3600,
+ '24h': 86400,
+ '7d': 604800,
+ '30d': 2592000
+ }
+ duration_seconds = duration_map.get(duration, 86400)
+ expires = current_time + duration_seconds
+
+ # Load existing banned IPs
+ banned_ips_file = '/etc/cyberpanel/banned_ips.json'
+ banned_ips = []
+ if os.path.exists(banned_ips_file):
+ try:
+ with open(banned_ips_file, 'r') as f:
+ banned_ips = json.load(f)
+ except:
+ banned_ips = []
+
+ # Check if IP is already banned
+ for banned_ip in banned_ips:
+ if banned_ip.get('ip') == ip and banned_ip.get('active', True):
+ final_dic = {'status': 0, 'error_message': f'IP address {ip} is already banned'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ # Add new banned IP
+ new_banned_ip = {
+ 'id': int(time.time()),
+ 'ip': ip,
+ 'reason': reason,
+ 'duration': duration,
+ 'banned_on': current_time,
+ 'expires': expires,
+ 'active': True
+ }
+ banned_ips.append(new_banned_ip)
+
+ # Ensure directory exists
+ os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
+
+ # Save to file
+ with open(banned_ips_file, 'w') as f:
+ json.dump(banned_ips, f, indent=2)
+
+ # Apply firewall rule to block the IP
+ try:
+ # Add iptables rule to block the IP
+ if '/' in ip:
+ # CIDR notation
+ subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'], check=True)
+ else:
+ # Single IP
+ subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'], check=True)
+
+ logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}')
+ except subprocess.CalledProcessError as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Failed to add iptables rule for {ip}: {str(e)}')
+
+ final_dic = {'status': 1, 'message': f'IP address {ip} has been banned successfully'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ except BaseException as msg:
+ final_dic = {'status': 0, 'error_message': str(msg)}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ def removeBannedIP(self, userID=None, data=None):
+ """
+ Remove/unban an IP address
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ banned_ip_id = data.get('id')
+
+ # Load existing banned IPs
+ banned_ips_file = '/etc/cyberpanel/banned_ips.json'
+ banned_ips = []
+ if os.path.exists(banned_ips_file):
+ try:
+ with open(banned_ips_file, 'r') as f:
+ banned_ips = json.load(f)
+ except:
+ banned_ips = []
+
+ # Find and update the banned IP
+ ip_to_unban = None
+ for banned_ip in banned_ips:
+ if banned_ip.get('id') == banned_ip_id:
+ banned_ip['active'] = False
+ banned_ip['unbanned_on'] = time.time()
+ ip_to_unban = banned_ip['ip']
+ break
+
+ if not ip_to_unban:
+ final_dic = {'status': 0, 'error_message': 'Banned IP not found'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ # Save updated banned IPs
+ with open(banned_ips_file, 'w') as f:
+ json.dump(banned_ips, f, indent=2)
+
+ # Remove iptables rule
+ try:
+ # Remove iptables rule to unblock the IP
+ if '/' in ip_to_unban:
+ # CIDR notation
+ subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_unban, '-j', 'DROP'], check=False)
+ else:
+ # Single IP
+ subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_unban, '-j', 'DROP'], check=False)
+
+ logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}')
+ except subprocess.CalledProcessError as e:
+ logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove iptables rule for {ip_to_unban}: {str(e)}')
+
+ final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ except BaseException as msg:
+ final_dic = {'status': 0, 'error_message': str(msg)}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ def deleteBannedIP(self, userID=None, data=None):
+ """
+ Permanently delete a banned IP record
+ """
+ try:
+ admin = Administrator.objects.get(pk=userID)
+ if admin.acl.adminStatus != 1:
+ return ACLManager.loadError()
+
+ banned_ip_id = data.get('id')
+
+ # Load existing banned IPs
+ banned_ips_file = '/etc/cyberpanel/banned_ips.json'
+ banned_ips = []
+ if os.path.exists(banned_ips_file):
+ try:
+ with open(banned_ips_file, 'r') as f:
+ banned_ips = json.load(f)
+ except:
+ banned_ips = []
+
+ # Find and remove the banned IP
+ ip_to_delete = None
+ updated_banned_ips = []
+ for banned_ip in banned_ips:
+ if banned_ip.get('id') == banned_ip_id:
+ ip_to_delete = banned_ip['ip']
+ else:
+ updated_banned_ips.append(banned_ip)
+
+ if not ip_to_delete:
+ final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ # Save updated banned IPs
+ with open(banned_ips_file, 'w') as f:
+ json.dump(updated_banned_ips, f, indent=2)
+
+ logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
+
+ final_dic = {'status': 1, 'message': f'Banned IP record for {ip_to_delete} has been deleted successfully'}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
+ except BaseException as msg:
+ final_dic = {'status': 0, 'error_message': str(msg)}
+ final_json = json.dumps(final_dic)
+ return HttpResponse(final_json)
+
diff --git a/firewall/static/firewall/firewall.js b/firewall/static/firewall/firewall.js
index 1d666ed78..117f057f2 100644
--- a/firewall/static/firewall/firewall.js
+++ b/firewall/static/firewall/firewall.js
@@ -16,9 +16,21 @@ app.controller('firewallController', function ($scope, $http) {
$scope.couldNotConnect = true;
$scope.rulesDetails = false;
+ // Banned IPs variables
+ $scope.activeTab = 'rules';
+ $scope.bannedIPs = [];
+ $scope.bannedIPsLoading = false;
+ $scope.bannedIPActionFailed = true;
+ $scope.bannedIPActionSuccess = true;
+ $scope.bannedIPCouldNotConnect = true;
+ $scope.banIP = '';
+ $scope.banReason = '';
+ $scope.banDuration = '24h';
+
firewallStatus();
populateCurrentRecords();
+ populateBannedIPs();
$scope.addRule = function () {
@@ -2393,4 +2405,140 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
}
}
+ // Banned IPs Functions
+ function populateBannedIPs() {
+ $scope.bannedIPsLoading = true;
+ var url = "/firewall/getBannedIPs";
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, {}, config).then(function(response) {
+ $scope.bannedIPsLoading = false;
+ if (response.data.status === 1) {
+ $scope.bannedIPs = response.data.bannedIPs || [];
+ } else {
+ $scope.bannedIPs = [];
+ $scope.bannedIPActionFailed = false;
+ $scope.bannedIPErrorMessage = response.data.error_message;
+ }
+ }, function(error) {
+ $scope.bannedIPsLoading = false;
+ $scope.bannedIPCouldNotConnect = false;
+ });
+ }
+
+ $scope.addBannedIP = function() {
+ if (!$scope.banIP || !$scope.banReason) {
+ $scope.bannedIPActionFailed = false;
+ $scope.bannedIPErrorMessage = "Please fill in all required fields";
+ return;
+ }
+
+ $scope.bannedIPsLoading = true;
+ $scope.bannedIPActionFailed = true;
+ $scope.bannedIPActionSuccess = true;
+ $scope.bannedIPCouldNotConnect = true;
+
+ var data = {
+ ip: $scope.banIP,
+ reason: $scope.banReason,
+ duration: $scope.banDuration
+ };
+
+ var url = "/firewall/addBannedIP";
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ $scope.bannedIPsLoading = false;
+ if (response.data.status === 1) {
+ $scope.bannedIPActionSuccess = false;
+ $scope.banIP = '';
+ $scope.banReason = '';
+ $scope.banDuration = '24h';
+ populateBannedIPs(); // Refresh the list
+ } else {
+ $scope.bannedIPActionFailed = false;
+ $scope.bannedIPErrorMessage = response.data.error_message;
+ }
+ }, function(error) {
+ $scope.bannedIPsLoading = false;
+ $scope.bannedIPCouldNotConnect = false;
+ });
+ };
+
+ $scope.removeBannedIP = function(id, ip) {
+ if (!confirm('Are you sure you want to unban IP address ' + ip + '?')) {
+ return;
+ }
+
+ $scope.bannedIPsLoading = true;
+ $scope.bannedIPActionFailed = true;
+ $scope.bannedIPActionSuccess = true;
+ $scope.bannedIPCouldNotConnect = true;
+
+ var data = { id: id };
+
+ var url = "/firewall/removeBannedIP";
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ $scope.bannedIPsLoading = false;
+ if (response.data.status === 1) {
+ $scope.bannedIPActionSuccess = false;
+ populateBannedIPs(); // Refresh the list
+ } else {
+ $scope.bannedIPActionFailed = false;
+ $scope.bannedIPErrorMessage = response.data.error_message;
+ }
+ }, function(error) {
+ $scope.bannedIPsLoading = false;
+ $scope.bannedIPCouldNotConnect = false;
+ });
+ };
+
+ $scope.deleteBannedIP = function(id, ip) {
+ if (!confirm('Are you sure you want to permanently delete the record for IP address ' + ip + '? This action cannot be undone.')) {
+ return;
+ }
+
+ $scope.bannedIPsLoading = true;
+ $scope.bannedIPActionFailed = true;
+ $scope.bannedIPActionSuccess = true;
+ $scope.bannedIPCouldNotConnect = true;
+
+ var data = { id: id };
+
+ var url = "/firewall/deleteBannedIP";
+ var config = {
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken')
+ }
+ };
+
+ $http.post(url, data, config).then(function(response) {
+ $scope.bannedIPsLoading = false;
+ if (response.data.status === 1) {
+ $scope.bannedIPActionSuccess = false;
+ populateBannedIPs(); // Refresh the list
+ } else {
+ $scope.bannedIPActionFailed = false;
+ $scope.bannedIPErrorMessage = response.data.error_message;
+ }
+ }, function(error) {
+ $scope.bannedIPsLoading = false;
+ $scope.bannedIPCouldNotConnect = false;
+ });
+ };
+
});
\ No newline at end of file
diff --git a/firewall/templates/firewall/firewall.html b/firewall/templates/firewall/firewall.html
index dace7774b..2081c94ed 100644
--- a/firewall/templates/firewall/firewall.html
+++ b/firewall/templates/firewall/firewall.html
@@ -526,6 +526,265 @@
min-width: 600px;
}
}
+
+ /* Tab Navigation Styles */
+ .tab-navigation {
+ display: flex;
+ background: var(--bg-secondary, white);
+ border-radius: 12px;
+ padding: 0.5rem;
+ margin-bottom: 2rem;
+ box-shadow: 0 2px 8px var(--shadow-light, rgba(0,0,0,0.1));
+ border: 1px solid var(--border-color, #e8e9ff);
+ }
+
+ .tab-button {
+ flex: 1;
+ padding: 1rem 1.5rem;
+ background: transparent;
+ border: none;
+ border-radius: 8px;
+ color: var(--text-secondary, #64748b);
+ font-weight: 500;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ }
+
+ .tab-button:hover {
+ background: var(--bg-hover, #f8f9ff);
+ color: var(--accent-color, #5b5fcf);
+ }
+
+ .tab-button.tab-active {
+ background: var(--accent-color, #5b5fcf);
+ color: var(--bg-secondary, white);
+ box-shadow: 0 2px 8px rgba(91, 95, 207, 0.3);
+ }
+
+ .tab-button i {
+ font-size: 1rem;
+ }
+
+ /* Banned IPs Panel Styles */
+ .banned-ips-panel {
+ background: var(--bg-secondary, white);
+ border-radius: 16px;
+ box-shadow: 0 4px 12px var(--shadow-medium, rgba(0,0,0,0.15));
+ border: 1px solid var(--border-color, #e8e9ff);
+ overflow: hidden;
+ }
+
+ .add-banned-section {
+ padding: 2rem;
+ border-bottom: 1px solid var(--border-light, #f3f4f6);
+ background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
+ }
+
+ .banned-form {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr auto;
+ gap: 1rem;
+ align-items: end;
+ }
+
+ .btn-ban {
+ background: var(--danger-color, #ef4444);
+ color: var(--bg-secondary, white);
+ border: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ height: fit-content;
+ }
+
+ .btn-ban:hover {
+ background: var(--danger-hover, #dc2626);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
+ }
+
+ .banned-list-section {
+ padding: 2rem;
+ }
+
+ .banned-table {
+ width: 100%;
+ border-collapse: collapse;
+ background: var(--bg-secondary, white);
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 8px var(--shadow-light, rgba(0,0,0,0.1));
+ }
+
+ .banned-table th {
+ background: linear-gradient(135deg, var(--bg-hover, #f8f9ff) 0%, var(--bg-gradient, #f0f1ff) 100%);
+ padding: 1rem;
+ text-align: left;
+ font-weight: 600;
+ color: var(--text-primary, #1e293b);
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ border-bottom: 2px solid var(--border-color, #e8e9ff);
+ }
+
+ .banned-table td {
+ padding: 1rem;
+ color: var(--text-secondary, #64748b);
+ font-size: 0.875rem;
+ border-bottom: 1px solid var(--border-light, #f3f4f6);
+ vertical-align: middle;
+ }
+
+ .banned-table tbody tr {
+ transition: all 0.2s ease;
+ }
+
+ .banned-table tbody tr:hover {
+ background: var(--bg-hover, #f8f9ff);
+ }
+
+ .ip-address {
+ font-weight: 600;
+ color: var(--text-primary, #1e293b);
+ font-family: 'Courier New', monospace;
+ }
+
+ .ip-address i {
+ color: var(--accent-color, #5b5fcf);
+ margin-right: 0.5rem;
+ }
+
+ .reason-text {
+ background: var(--bg-light, #f8f9fa);
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ color: var(--text-secondary, #64748b);
+ }
+
+ .banned-date, .expires-date {
+ color: var(--text-secondary, #64748b);
+ font-size: 0.8rem;
+ }
+
+ .banned-date i, .expires-date i {
+ color: var(--accent-color, #5b5fcf);
+ margin-right: 0.25rem;
+ }
+
+ .status-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.25rem 0.75rem;
+ border-radius: 12px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+
+ .status-badge.status-active {
+ background: rgba(239, 68, 68, 0.1);
+ color: var(--danger-color, #ef4444);
+ }
+
+ .status-badge.status-expired {
+ background: rgba(107, 114, 128, 0.1);
+ color: #6b7280;
+ }
+
+ .status-badge i {
+ font-size: 0.5rem;
+ }
+
+ .actions {
+ display: flex;
+ gap: 0.5rem;
+ }
+
+ .btn-unban {
+ background: var(--success-color, #10b981);
+ color: var(--bg-secondary, white);
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ font-size: 0.8rem;
+ }
+
+ .btn-unban:hover {
+ background: var(--success-hover, #059669);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+ }
+
+ .btn-delete {
+ background: var(--danger-color, #ef4444);
+ color: var(--bg-secondary, white);
+ border: none;
+ padding: 0.5rem;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .btn-delete:hover {
+ background: var(--danger-hover, #dc2626);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+ }
+
+ /* Responsive Design for Banned IPs */
+ @media (max-width: 768px) {
+ .banned-form {
+ grid-template-columns: 1fr;
+ gap: 1rem;
+ }
+
+ .btn-ban {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .banned-list-section {
+ padding: 1rem;
+ overflow-x: auto;
+ }
+
+ .banned-table {
+ min-width: 800px;
+ }
+
+ .tab-navigation {
+ flex-direction: column;
+ }
+
+ .tab-button {
+ width: 100%;
+ }
+ }
{% endblock %}
@@ -591,8 +850,26 @@
+
+
+
-
|---|
| {% trans "IP Address" %} | +{% trans "Reason" %} | +{% trans "Banned On" %} | +{% trans "Expires" %} | +{% trans "Status" %} | +{% trans "Actions" %} | +
|---|---|---|---|---|---|
| + + {$ bannedIP.ip $} + | ++ {$ bannedIP.reason $} + | ++ + {$ bannedIP.banned_on | date:'MMM dd, yyyy HH:mm' $} + | ++ + {% trans "Never" %} + {$ bannedIP.expires | date:'MMM dd, yyyy HH:mm' $} + | ++ + + {$ bannedIP.active ? 'Active' : 'Expired' $} + + | ++ + + | +
{% trans "All IP addresses are currently allowed. Add banned IPs to block suspicious or malicious traffic." %}
+