From 59ac92dd31d37689836fac372f944951da325f47 Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 12:36:50 +0530 Subject: [PATCH 01/11] searchbar styling --- ui/qss/dark.qss | 13 +++++++++++++ ui/qss/light.qss | 19 ++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ui/qss/dark.qss b/ui/qss/dark.qss index 6e1dd82..3f86f00 100644 --- a/ui/qss/dark.qss +++ b/ui/qss/dark.qss @@ -185,4 +185,17 @@ QComboBox { QTableWidget QCheckBox { margin-left: 40px; +} + +QToolBar QLineEdit { + border-radius: 4px; + padding: 4px 6px; + color: rgb(221, 221, 221); + background-color: rgb(39, 40, 58); + margin: 0px 15px 5px 0px; + font: 15px; + selection-background-color: #BFDFFF; +} +QToolBar QLineEdit:focus { + border: 1px solid #7B66FF; } \ No newline at end of file diff --git a/ui/qss/light.qss b/ui/qss/light.qss index 9c29a68..7d90f6c 100644 --- a/ui/qss/light.qss +++ b/ui/qss/light.qss @@ -49,7 +49,7 @@ QMainWindow { } .btn { - background-color: #AA8F75; + background-color: #C6A788; padding: 4px 13px; border-radius: 5px; border: 1px solid #382F27; @@ -64,7 +64,7 @@ QMainWindow { } .add-btn { - background-color: #AA8F75; + background-color: #C6A788; padding: 2.5px 10px; border-radius: 5px; color: #382F27; @@ -76,7 +76,7 @@ QMainWindow { } .remove-btn { - background-color: #AA8F75; + background-color: #C6A788; padding: 1.5px 5px; border-radius: 5px; color: #382F27; @@ -186,4 +186,17 @@ QComboBox { QTableWidget QCheckBox { margin-left: 40px; +} + +QToolBar QLineEdit { + border-radius: 4px; + padding: 4px 6px; + color: #382F27; + background-color: #FFF1E4; + margin: 0px 15px 5px 0px; + font: 15px; + selection-background-color: #AA8F75; +} +QToolBar QLineEdit:focus { + border: 1px solid #AA8F75; } \ No newline at end of file From a4474cec96c62725397a5a5ee1473f16aac70ed4 Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 14:32:55 +0530 Subject: [PATCH 02/11] TOML Changes --- ui/components/software/gdm.py | 15 ++++++++++++--- ui/components/software/time_sync.py | 24 +++++++++++++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/ui/components/software/gdm.py b/ui/components/software/gdm.py index a34faa0..9fedf06 100644 --- a/ui/components/software/gdm.py +++ b/ui/components/software/gdm.py @@ -43,9 +43,9 @@ def init_ui(self): hlayout = QHBoxLayout() # Lock on Idle Label - self.lockon_lable = QLabel('Lock on Idle(seconds)') - self.lockon_lable.setToolTip(self.gdm_tooltip['lock_on_idle']) - self.lockon_lable.setProperty('class', 'normal-label-for') + self.lockon_lable = QCheckBox('Enable Lock on Idle (seconds): ') + self.lockon_lable.setToolTip(self.gdm_tooltip['enable_lock_on_idle']) + self.lockon_lable.stateChanged.connect(self.enable_lock_on_idle_changed) self.time_input = QLineEdit() self.time_input.setText(str(self.toml_gdm['lock_on_idle'])) @@ -70,6 +70,7 @@ def init_ui(self): def refresh_config(self, config): self.config = config self.toml_gdm = self.config['gdm'] + self.lockon_lable.setChecked(self.toml_gdm['enable_lock_on_idle']) for name, state in self.toml_gdm.items(): if name == 'lock_on_idle': continue @@ -100,4 +101,12 @@ def time_changed(self, new_size): self.toml_gdm['lock_on_idle'] = int(new_size) else: self.time_input.setText('0') + config_file.write(self.config) + + def enable_lock_on_idle_changed(self, state): + self.toml_gdm['enable_lock_on_idle'] = (state == 2) + if state == 2: + self.time_input.setEnabled(True) + else: + self.time_input.setEnabled(False) config_file.write(self.config) \ No newline at end of file diff --git a/ui/components/software/time_sync.py b/ui/components/software/time_sync.py index c8a4a6b..2f144b5 100644 --- a/ui/components/software/time_sync.py +++ b/ui/components/software/time_sync.py @@ -42,10 +42,10 @@ def init_ui(self): self.enable_user.stateChanged.connect(lambda state, name = 'enable_ntp_user': self.save_checkbox_state(name, state)) self.container_layout.addWidget(self.enable_user) - ntp_server_lable = QLabel('NTP Servers') - ntp_server_lable.setToolTip(self.time_sync_tooltip['ntp_servers']) - ntp_server_lable.setProperty('class', 'normal-label-for') - self.container_layout.addWidget(ntp_server_lable) + self.ntp_server_checkbox = QCheckBox('Enable NTP Servers') + self.ntp_server_checkbox.setToolTip(self.time_sync_tooltip['enable_ntp_servers']) + self.ntp_server_checkbox.stateChanged.connect(self.enable_ntp_servers_changed) + self.container_layout.addWidget(self.ntp_server_checkbox) hlayout = QHBoxLayout() @@ -121,7 +121,7 @@ def refresh_config(self, config): self.toml_time_sync = self.config['time-sync'] self.enable_ntp.setChecked(self.toml_time_sync['enable_ntp']) self.enable_user.setChecked(self.toml_time_sync['enable_ntp_user']) - + self.ntp_server_checkbox.setChecked(self.toml_time_sync['enable_ntp_servers']) if not self.toml_time_sync['enable_ntp']: self.enable_user.setEnabled(False) self.servers_table.setEnabled(False) @@ -144,3 +144,17 @@ def save_checkbox_state(self, name, state): for i in range(self.servers_table.rowCount()): self.servers_table.cellWidget(i, 1).setEnabled(state == 2) config_file.write(self.config) + + def enable_ntp_servers_changed(self, state): + self.toml_time_sync['enable_ntp_servers'] = (state == 2) + if state == 2: + self.new_server.setEnabled(True) + self.add_button.setEnabled(True) + for i in range(self.servers_table.rowCount()): + self.servers_table.cellWidget(i, 1).setEnabled(True) + else: + self.new_server.setEnabled(False) + self.add_button.setEnabled(False) + for i in range(self.servers_table.rowCount()): + self.servers_table.cellWidget(i, 1).setEnabled(False) + config_file.write(self.config) \ No newline at end of file From c2bd37331bb8348a32d39ac283101b2124a5c09a Mon Sep 17 00:00:00 2001 From: Abhishek M J Date: Wed, 20 Dec 2023 15:50:15 +0530 Subject: [PATCH 03/11] update levels with pam --- config/server/level-1.toml | 11 ++++++++++- config/server/level-2.toml | 11 ++++++++++- config/workstation/level-1.toml | 11 ++++++++++- config/workstation/level-2.toml | 11 ++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/config/server/level-1.toml b/config/server/level-1.toml index eee62f2..242182b 100644 --- a/config/server/level-1.toml +++ b/config/server/level-1.toml @@ -128,4 +128,13 @@ disable_nopasswd = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes -restrict_su = true \ No newline at end of file +restrict_su = true + +[pam] # PAM +enable_password_level = true +required_password_level = "strong" # weak, medium, strong, stronger +enable_password_length = true +minimum_password_length = 14 +limit_password_reuse = true +password_reuse_limit = 5 +configure_hashing_algorithm = true diff --git a/config/server/level-2.toml b/config/server/level-2.toml index 5858962..12b4a57 100644 --- a/config/server/level-2.toml +++ b/config/server/level-2.toml @@ -127,4 +127,13 @@ disable_nopasswd = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes -restrict_su = true \ No newline at end of file +restrict_su = true + +[pam] # PAM +enable_password_level = true +required_password_level = "strong" # weak, medium, strong, stronger +enable_password_length = true +minimum_password_length = 14 +limit_password_reuse = true +password_reuse_limit = 5 +configure_hashing_algorithm = true diff --git a/config/workstation/level-1.toml b/config/workstation/level-1.toml index 8978cc8..7d11214 100644 --- a/config/workstation/level-1.toml +++ b/config/workstation/level-1.toml @@ -124,4 +124,13 @@ disable_nopasswd = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes -restrict_su = true \ No newline at end of file +restrict_su = true + +[pam] # PAM +enable_password_level = true +required_password_level = "strong" # weak, medium, strong, stronger +enable_password_length = true +minimum_password_length = 14 +limit_password_reuse = true +password_reuse_limit = 5 +configure_hashing_algorithm = true diff --git a/config/workstation/level-2.toml b/config/workstation/level-2.toml index d81e663..f010ac5 100644 --- a/config/workstation/level-2.toml +++ b/config/workstation/level-2.toml @@ -127,4 +127,13 @@ disable_nopasswd = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes -restrict_su = true \ No newline at end of file +restrict_su = true + +[pam] # PAM +enable_password_level = true +required_password_level = "strong" # weak, medium, strong, stronger +enable_password_length = true +minimum_password_length = 14 +limit_password_reuse = true +password_reuse_limit = 5 +configure_hashing_algorithm = true From 43af870ad3f666cd8bbac816894c6044a7e4053f Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 15:50:29 +0530 Subject: [PATCH 04/11] Add PAM UI --- ui/components/software/pam.py | 148 ++++++++++++++++++++++++++++++++++ ui/pages/software_page.py | 9 ++- 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 ui/components/software/pam.py diff --git a/ui/components/software/pam.py b/ui/components/software/pam.py new file mode 100644 index 0000000..a89a44a --- /dev/null +++ b/ui/components/software/pam.py @@ -0,0 +1,148 @@ +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QCheckBox \ + , QHBoxLayout, QComboBox, QLineEdit +from harden import config_file +from PyQt6.QtGui import QIntValidator + +class PAM(QWidget): + def __init__(self, config, tooltip): + super().__init__() + self.config = config + self.tooltip = tooltip + self.toml_pam = self.config['pam'] + self.pam_tooltip = self.tooltip['pam'] + self.init_ui() + self.refresh_config(config) + + def init_ui(self): + self.layout = QVBoxLayout() + self.setLayout(self.layout) + self.layout.setSpacing(0) + self.layout.setContentsMargins(0, 0, 0, 0) + + self.main_label = QLabel("PAM") + self.layout.addWidget(self.main_label) + self.main_label.setObjectName("component-title") + + # container widget + self.container_widget = QWidget() + self.container_layout = QVBoxLayout() + self.container_widget.setLayout(self.container_layout) + self.layout.addWidget(self.container_widget) + self.container_layout.setSpacing(0) + self.container_layout.setContentsMargins(30, 30, 30, 30) + self.container_widget.setObjectName("container-widget") + + # Enable Password Checkbox + self.enable_password_checkbox = QCheckBox('Enable Password Level') + self.enable_password_checkbox.setToolTip(self.pam_tooltip['enable_password_level']) + self.enable_password_checkbox.stateChanged.connect(lambda state: self.save_checkbox_state(state, 'enable_password_level')) + self.container_layout.addWidget(self.enable_password_checkbox) + + # Enable Password Dropdown + hlayout = QHBoxLayout() + + # Select Mode Label + self.mode_label = QLabel('Required Password Level:') + self.mode_label.setToolTip(self.pam_tooltip['enable_password_level']) + self.mode_label.setProperty('class', 'normal-label-for') + + # Mode Dropdown + self.mode_list = QComboBox() + self.mode_list.addItems(['weak', 'medium', 'strong', 'stronger']) + self.mode_list.currentTextChanged.connect(lambda text: self.new_item_selected(text, 'required_password_level')) + + hlayout.addWidget(self.mode_label) + hlayout.addWidget(self.mode_list) + self.container_layout.addLayout(hlayout) + + # Enable Password Length Checkbox + self.enable_password_len_checkbox = QCheckBox('Enable Password Length') + self.enable_password_len_checkbox.setToolTip(self.pam_tooltip['enable_password_length']) + self.enable_password_len_checkbox.stateChanged.connect(lambda state: self.save_checkbox_state(state, 'enable_password_length')) + self.container_layout.addWidget(self.enable_password_len_checkbox) + + # Enable Password Dropdown + hlayout = QHBoxLayout() + + self.len_label = QLabel('Minimum Password Length: ') + self.len_label.setToolTip(self.pam_tooltip['enable_password_length']) + self.len_label.setProperty('class', 'normal-label-for') + + self.size_input = QLineEdit() + validator = QIntValidator() + self.size_input.setValidator(validator) + self.size_input.textChanged.connect(lambda text: self.size_changed(text, 'minimum_password_length', self.size_input)) + + hlayout.addWidget(self.len_label) + hlayout.addWidget(self.size_input) + self.container_layout.addLayout(hlayout) + + # Enable Password Length Checkbox + self.limit_password_reuse_checkbox = QCheckBox('Enable Limit Password Reuse') + self.limit_password_reuse_checkbox.setToolTip(self.pam_tooltip['limit_password_reuse']) + self.limit_password_reuse_checkbox.stateChanged.connect(lambda state: self.save_checkbox_state(state, 'limit_password_reuse')) + self.container_layout.addWidget(self.limit_password_reuse_checkbox) + + # Enable Password Dropdown + hlayout = QHBoxLayout() + + self.reuse_label = QLabel('Minimum Password Length: ') + self.reuse_label.setToolTip(self.pam_tooltip['limit_password_reuse']) + self.reuse_label.setProperty('class', 'normal-label-for') + + self.size_input_2 = QLineEdit() + validator = QIntValidator() + self.size_input_2.setValidator(validator) + self.size_input_2.textChanged.connect(lambda text: self.size_changed(text, 'password_reuse_limit', self.size_input_2)) + + hlayout.addWidget(self.reuse_label) + hlayout.addWidget(self.size_input_2) + self.container_layout.addLayout(hlayout) + + # Configure Hashing Algorithm + self.configure_hashing_algorithm = QCheckBox('Configure Hashing Algorithm') + self.configure_hashing_algorithm.setToolTip(self.pam_tooltip['configure_hashing_algorithm']) + self.configure_hashing_algorithm.stateChanged.connect(lambda state: self.save_checkbox_state(state, 'configure_hashing_algorithm')) + self.container_layout.addWidget(self.configure_hashing_algorithm) + + def refresh_config(self, config): + self.config = config + self.toml_pam = self.config['pam'] + self.enable_password_checkbox.setChecked(self.toml_pam['enable_password_level']) + self.enable_password_len_checkbox.setChecked(self.toml_pam['enable_password_length']) + self.limit_password_reuse_checkbox.setChecked(self.toml_pam['limit_password_reuse']) + self.configure_hashing_algorithm.setChecked(self.toml_pam['configure_hashing_algorithm']) + self.mode_list.setCurrentText(self.toml_pam['required_password_level']) + self.size_input.setText(str(self.toml_pam['minimum_password_length'])) + self.size_input_2.setText(str(self.toml_pam['password_reuse_limit'])) + + def save_checkbox_state(self, state, key): + self.toml_pam[key] = (state == 2) + if state == 0: + if key == 'enable_password_level': + self.mode_list.setEnabled(False) + elif key == 'enable_password_length': + self.size_input.setEnabled(False) + elif key == 'limit_password_reuse': + self.size_input_2.setEnabled(False) + else: + if key == 'enable_password_level': + self.mode_list.setEnabled(True) + elif key == 'enable_password_length': + self.size_input.setEnabled(True) + elif key == 'limit_password_reuse': + self.size_input_2.setEnabled(True) + config_file.write(self.config) + + def new_item_selected(self, text, key): + self.toml_pam[key] = text + config_file.write(self.config) + + def size_changed(self, new_size, key, input): + if new_size.startswith('0') and len(new_size) > 1: + input.setText(new_size[1:]) + if new_size: + self.toml_pam[key] = int(new_size) + else: + input.setText('0') + config_file.write(self.config) \ No newline at end of file diff --git a/ui/pages/software_page.py b/ui/pages/software_page.py index 94ae49b..5963579 100644 --- a/ui/pages/software_page.py +++ b/ui/pages/software_page.py @@ -6,6 +6,7 @@ from ui.components.software.services import Services from ui.components.software.service_clients import ServiceClients from ui.components.software.privilege_escalation import PrivilegeEscalation +from ui.components.software.pam import PAM from PyQt6.QtCore import Qt @@ -51,6 +52,10 @@ def init_ui(self): self.privilege_escalation = PrivilegeEscalation(self.config, self.tooltip) self.privilege_escalation.setObjectName("privilege_escalation") + # PAM Widget + self.pam = PAM(self.config, self.tooltip) + self.pam.setObjectName("pam") + self.layout.addWidget(self.process_hardening) self.layout.addWidget(self.apparmor) self.layout.addWidget(self.gdm) @@ -58,6 +63,7 @@ def init_ui(self): self.layout.addWidget(self.services) self.layout.addWidget(self.service_clients) self.layout.addWidget(self.privilege_escalation) + self.layout.addWidget(self.pam) def refresh_config(self, config): self.config = config @@ -67,4 +73,5 @@ def refresh_config(self, config): self.time_sync.refresh_config(config) self.services.refresh_config(config) self.service_clients.refresh_config(config) - self.privilege_escalation.refresh_config(config) \ No newline at end of file + self.privilege_escalation.refresh_config(config) + self.pam.refresh_config(config) \ No newline at end of file From 5c3b87df1fe9563d02c6c8c57bf92f4a9440f06c Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 15:50:53 +0530 Subject: [PATCH 05/11] Refactor Add Button --- ui/components/software/time_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/software/time_sync.py b/ui/components/software/time_sync.py index 2f144b5..97bf41a 100644 --- a/ui/components/software/time_sync.py +++ b/ui/components/software/time_sync.py @@ -54,8 +54,8 @@ def init_ui(self): self.add_button.clicked.connect(self.add_new_server) self.add_button.setProperty('class', 'add-btn') - hlayout.addWidget(self.new_server) hlayout.addWidget(self.add_button) + hlayout.addWidget(self.new_server) self.container_layout.addLayout(hlayout) From 4bc381a776fa53510d0d00dd82721e01eb4b3daf Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 15:51:01 +0530 Subject: [PATCH 06/11] Change Font Size --- ui/qss/light.qss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/qss/light.qss b/ui/qss/light.qss index 7d90f6c..8107d6a 100644 --- a/ui/qss/light.qss +++ b/ui/qss/light.qss @@ -68,7 +68,9 @@ QMainWindow { padding: 2.5px 10px; border-radius: 5px; color: #382F27; - font: 18px; + font: 14px; + margin-right: 10px; + margin-bottom: 5px; font-weight: 500; } .add-btn:hover { @@ -146,7 +148,7 @@ QLineEdit { border-radius: 4px; padding: 2px 5px; color: #382F27; - background-color: #FFF6ED; + background-color: #ffffff; margin: 0px 15px 5px 0px; selection-background-color: #AA8F75; } @@ -175,8 +177,10 @@ QComboBox { border-radius: 4px; padding: 2px 5px; color: #382F27; - background-color: #FFF6ED; + background-color: #ffffff; margin: 0px 15px 5px 0px; + font: 15px; + font-weight: 400; selection-background-color: #AA8F75; } From 43a30fa3f2c6ba52a8b19bdcd3556ce8e1dd6a05 Mon Sep 17 00:00:00 2001 From: Siddesh Devarakonda Date: Wed, 20 Dec 2023 16:13:53 +0530 Subject: [PATCH 07/11] fix key error in privilege escalation --- config/server/level-1.toml | 2 +- config/server/level-2.toml | 2 +- config/workstation/level-1.toml | 2 +- config/workstation/level-2.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/server/level-1.toml b/config/server/level-1.toml index 242182b..3497389 100644 --- a/config/server/level-1.toml +++ b/config/server/level-1.toml @@ -124,7 +124,7 @@ client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation use_pty = true enable_logfile = true -disable_nopasswd = false +disable_nopassword = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes diff --git a/config/server/level-2.toml b/config/server/level-2.toml index 12b4a57..9769535 100644 --- a/config/server/level-2.toml +++ b/config/server/level-2.toml @@ -123,7 +123,7 @@ client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation use_pty = true enable_logfile = true -disable_nopasswd = false +disable_nopassword = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes diff --git a/config/workstation/level-1.toml b/config/workstation/level-1.toml index 7d11214..606dc8b 100644 --- a/config/workstation/level-1.toml +++ b/config/workstation/level-1.toml @@ -120,7 +120,7 @@ client_alive_count_max = {enable = true, value = 3} [privilege_escalation] # Privilege Escalation use_pty = true enable_logfile = true -disable_nopasswd = false +disable_nopassword = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes diff --git a/config/workstation/level-2.toml b/config/workstation/level-2.toml index f010ac5..edf5364 100644 --- a/config/workstation/level-2.toml +++ b/config/workstation/level-2.toml @@ -123,7 +123,7 @@ client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation use_pty = true enable_logfile = true -disable_nopasswd = false +disable_nopassword = false enable_reauthentication = true enable_authentication_timeout = true authentication_timeout = 15 # in minutes From d0180625d5731a6028978d0908e7e76ed8ea1b4b Mon Sep 17 00:00:00 2001 From: Sri Sujan Date: Wed, 20 Dec 2023 16:14:18 +0530 Subject: [PATCH 08/11] Update ssh and privilege_escalation --- config/sampleconfig.toml | 2 +- ui/components/network/ssh.py | 104 +++++++++++++----- .../software/privilege_escalation.py | 17 +-- 3 files changed, 86 insertions(+), 37 deletions(-) diff --git a/config/sampleconfig.toml b/config/sampleconfig.toml index f29b13a..5935853 100644 --- a/config/sampleconfig.toml +++ b/config/sampleconfig.toml @@ -110,9 +110,9 @@ enable_strong_mac_algorithms = true enable_strong_key_exchange_algorithms = true disable_tcp_forwarding = false configure_warning_banner = true +configure_max_startups = true enable_max_auth_tries = true max_auth_tries = 4 -configure_max_startups = true enable_max_sessions = true max_sessions = 10 enable_login_grace_time = true diff --git a/ui/components/network/ssh.py b/ui/components/network/ssh.py index 4faa142..afa3fd5 100644 --- a/ui/components/network/ssh.py +++ b/ui/components/network/ssh.py @@ -45,9 +45,11 @@ def init_ui(self): self.container_layout.addWidget(checkbox) self.configure_permissions_checkboxes[name] = checkbox - self.allow_users_label = QLabel("Allow Users") - self.container_layout.addWidget(self.allow_users_label) - self.allow_users_label.setObjectName("sub-component-title") + self.allow_users_checkbox = QCheckBox('Allow Users') + self.allow_users_checkbox.stateChanged.connect(lambda state: self.allow_users(state)) + self.allow_users_checkbox.setProperty('class', 'in-checkbox') + self.container_layout.addWidget(self.allow_users_checkbox) + hlayout = QHBoxLayout() self.container_layout.addLayout(hlayout) @@ -62,9 +64,10 @@ def init_ui(self): self.user_table() - self.allow_groups_label = QLabel("Allow Groups") - self.container_layout.addWidget(self.allow_groups_label) - self.allow_groups_label.setObjectName("sub-component-title") + self.allow_groups_checkbox = QCheckBox('Allow Groups') + self.allow_groups_checkbox.stateChanged.connect(lambda state: self.allow_groups(state)) + self.allow_groups_checkbox.setProperty('class', 'in-checkbox') + self.container_layout.addWidget(self.allow_groups_checkbox) hlayout = QHBoxLayout() @@ -83,47 +86,74 @@ def init_ui(self): hlayout = QHBoxLayout() - self.log_level_label = QLabel('Log Level:') - self.log_level_label.setToolTip(self.ssh_tooltip['log_level']) - self.log_level_label.setProperty('class', 'normal-label-for') + self.log_level_check = QCheckBox('Log Level') + self.log_level_check.stateChanged.connect(lambda state: self.save_checkbox_state('log_level', state)) + self.log_level_check.setProperty('class', 'in-checkbox') self.log_level_list = QComboBox() self.log_level_list.addItems(['VERBOSE', 'INFO']) self.log_level_list.currentTextChanged.connect(self.new_item_selected) - hlayout.addWidget(self.log_level_label) + hlayout.addWidget(self.log_level_check) hlayout.addWidget(self.log_level_list) self.container_layout.addLayout(hlayout) self.ssh_checkboxes = {} self.ssh_inputs = {} i = 0 - for name, state in self.toml_ssh.items(): - if i < 4: + self.names = list(self.toml_ssh.keys()) + while i < len(self.names): + if i < 7: i += 1 continue - elif i <= 17 and name != 'max_auth_tries': + elif i < 20: + name = self.names[i] checkbox = QCheckBox(f"{name.replace('_',' ').title()}") checkbox.setToolTip(self.ssh_tooltip[name]) checkbox.stateChanged.connect(lambda state, name=name: self.save_checkbox_state(name, state)) - self.ssh_checkboxes[name] = checkbox + checkbox.setProperty('class', 'in-checkbox') self.container_layout.addWidget(checkbox) - elif i > 17 or name == 'max_auth_tries': + self.ssh_checkboxes[name] = checkbox + else: + name = self.names[i] hlayout = QHBoxLayout() - label = QLabel(f"{name.replace('_',' ').title()}") - label.setToolTip(self.ssh_tooltip[name]) - label.setProperty('class', 'normal-label-for') + checkbox = QCheckBox(f"{name.replace('_',' ').title()}") + checkbox.setToolTip(self.ssh_tooltip[name]) + checkbox.stateChanged.connect(lambda state, name=name: self.save_checkbox_state(name, state)) + checkbox.setProperty('class', 'in-checkbox') + self.ssh_checkboxes[name] = checkbox + i += 1 + name = self.names[i] input = QLineEdit() - input.setText(str(state)) - validator = QIntValidator() - input.setValidator(validator) + input.setValidator(QIntValidator()) input.textChanged.connect(lambda text, name=name: self.save_text_input(name, text)) - hlayout.addWidget(label) + self.ssh_inputs[name] = input + hlayout.addWidget(checkbox) hlayout.addWidget(input) self.container_layout.addLayout(hlayout) - self.ssh_inputs[name] = input i += 1 + + def allow_users(self, state): + if state == 2: + self.new_user.setEnabled(True) + self.add_user_button.setEnabled(True) + self.users_table.setEnabled(True) + else: + self.new_user.setEnabled(False) + self.add_user_button.setEnabled(False) + self.users_table.setEnabled(False) + + def allow_groups(self, state): + if state == 2: + self.new_group.setEnabled(True) + self.add_group_button.setEnabled(True) + self.groups_table.setEnabled(True) + else: + self.new_group.setEnabled(False) + self.add_group_button.setEnabled(False) + self.groups_table.setEnabled(False) + def user_table(self): self.users_table = QTableWidget() self.users_table.setColumnCount(2) @@ -224,6 +254,14 @@ def new_item_selected(self, text): def save_checkbox_state(self, name, state): self.toml_ssh[name] = (state == 2) config_file.write(self.config) + if name == 'log_level': + self.log_level_list.setEnabled(state == 2) + for i in self.ssh_checkboxes: + if i == name: + for j in self.ssh_inputs: + if name.endswith(j): + self.ssh_inputs[j].setEnabled(state == 2) + break def save_checkbox_state_configure(self, state, category, name): self.toml_ssh[category][name] = (state == 2) @@ -241,15 +279,23 @@ def refresh_config(self, config): self.toml_ssh = self.config['ssh'] for name, state in self.toml_ssh['configure_permissions'].items(): self.configure_permissions_checkboxes[name].setChecked(state) + self.allow_users_checkbox.setChecked(self.toml_ssh['enable_allow_users']) + self.allow_groups_checkbox.setChecked(self.toml_ssh['enable_allow_groups']) + self.log_level_check.setChecked(self.toml_ssh['enable_log_level']) i = 0 - for name, state in self.toml_ssh.items(): - if i < 4: + while i < len(self.names): + if i < 7: i += 1 continue - elif i <= 17 and name != 'max_auth_tries': - self.ssh_checkboxes[name].setChecked(state) - elif i > 17 or name == 'max_auth_tries': - self.ssh_inputs[name].setText(str(state)) + elif i < 20: + name = self.names[i] + self.ssh_checkboxes[name].setChecked(self.toml_ssh[name]) + else: + name = self.names[i] + self.ssh_checkboxes[name].setChecked(self.toml_ssh[name]) + i += 1 + name = self.names[i] + self.ssh_inputs[name].setText(str(self.toml_ssh[name])) i += 1 self.users_table.setRowCount(0) diff --git a/ui/components/software/privilege_escalation.py b/ui/components/software/privilege_escalation.py index e023839..8ed9f5b 100644 --- a/ui/components/software/privilege_escalation.py +++ b/ui/components/software/privilege_escalation.py @@ -35,9 +35,9 @@ def init_ui(self): self.checkboxes = {} for name, state in self.toml_privilege_escalation.items(): - if name == 'authentication_timeout': + if name == 'authentication_timeout' or name == 'enable_authentication_timeout': continue - checkbox = QCheckBox(name) + checkbox = QCheckBox(name.replace('_', ' ').title()) self.checkboxes[name] = checkbox checkbox.setToolTip(self.privilege_escalation_tooltip[name]) checkbox.stateChanged.connect(lambda state, name=name: self.save_checkbox_state(state, name)) @@ -48,15 +48,15 @@ def init_ui(self): hlayout.setSpacing(0) hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setAlignment(Qt.AlignmentFlag.AlignLeft) - self.configure_label = QLabel('Authentication Timeout (minutes): ') - self.configure_label.setToolTip(self.privilege_escalation_tooltip['authentication_timeout']) + self.configure_checkbox = QCheckBox("Authentication Timeout") + self.configure_checkbox.stateChanged.connect(lambda state, name='enable_authentication_timeout': self.save_checkbox_state(state, name)) + self.configure_checkbox.setToolTip(self.privilege_escalation_tooltip['authentication_timeout']) self.time_input = QLineEdit() validator = QIntValidator() self.time_input.setValidator(validator) self.time_input.textChanged.connect(self.time_changed) - self.configure_label.setProperty('class', 'normal-label-for') - hlayout.addWidget(self.configure_label) + hlayout.addWidget(self.configure_checkbox) hlayout.addWidget(self.time_input) self.container_layout.addLayout(hlayout) @@ -64,16 +64,19 @@ def refresh_config(self, config): self.config = config self.toml_privilege_escalation = self.config['privilege_escalation'] for name, state in self.toml_privilege_escalation.items(): - if name == 'authentication_timeout': + if name == 'authentication_timeout' or name == 'enable_authentication_timeout': continue checkbox = self.checkboxes[name] checkbox.setChecked(state) + self.configure_checkbox.setChecked(self.toml_privilege_escalation['enable_authentication_timeout']) self.time_input.setText(str(self.toml_privilege_escalation['authentication_timeout'])) def save_checkbox_state(self, state, name): self.toml_privilege_escalation[name] = (state == 2) config_file.write(self.config) + if name == 'enable_authentication_timeout': + self.time_input.setEnabled(state == 2) def time_changed(self, new_size): if new_size.startswith('0') and len(new_size) > 1: From df1069d4a217ebe2e726bf7ea1fcee8b334c6d8f Mon Sep 17 00:00:00 2001 From: Abhishek M J Date: Wed, 20 Dec 2023 16:16:22 +0530 Subject: [PATCH 09/11] Add profiles in config --- harden/config_file.py | 95 +++++++++++++++++++++++++++++-------------- harden/script.py | 5 ++- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/harden/config_file.py b/harden/config_file.py index ffc0057..9ad8bce 100644 --- a/harden/config_file.py +++ b/harden/config_file.py @@ -4,19 +4,25 @@ from typing import Mapping from harden import physical_ports -FILE_PATH = "" -TEMP_FILE_PATH = "" +# Config directory of user +CONFIG_DIR = os.path.expanduser("~/.config/HardeningHub") +PROFILE_DIR = os.path.join(CONFIG_DIR, "profiles") +DEFAULT_CONFIG_PATH = os.path.expanduser("~/.config/HardeningHub/default_config.toml") +TEMP_FILE_PATH = DEFAULT_CONFIG_PATH + ".tmp" +SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "../config/sampleconfig.toml") -def create_copy(): - shutil.copyfile(FILE_PATH, TEMP_FILE_PATH) +def create_copy(file_path: str = DEFAULT_CONFIG_PATH, temp_file_path: str = None): + global TEMP_FILE_PATH + if temp_file_path is None: + temp_file_path = file_path + ".tmp" + TEMP_FILE_PATH = temp_file_path + shutil.copyfile(file_path, TEMP_FILE_PATH) def read(file_path: str = None): if file_path is None: file_path = TEMP_FILE_PATH - if not os.path.exists(file_path): # Check if the copy does not exist - create_copy() # Create the copy if it doesn't exist with open(file_path, "r") as f: return tomlkit.load(f) @@ -28,10 +34,61 @@ def write(config: Mapping): def save(file_path: str = None): if file_path is None: - file_path = FILE_PATH + file_path = TEMP_FILE_PATH.replace(".tmp", "") shutil.copyfile(TEMP_FILE_PATH, file_path) +def get_profiles(): + if not os.path.exists(PROFILE_DIR): + init_config_dir() + return [] + + profiles = os.listdir(PROFILE_DIR) + for i in range(len(profiles)): + profiles[i] = profiles[i].replace("_config.toml", "") + + return profiles + + +def get_profile_path(profile_name: str): + return os.path.join(PROFILE_DIR, profile_name + "_config.toml") + + +def init_config_dir(): + # Create the config directory if it doesn't exist + if not os.path.exists(CONFIG_DIR): + os.makedirs(CONFIG_DIR) + os.makedirs(PROFILE_DIR) + # Create the default config file if it doesn't exist + if not os.path.exists(DEFAULT_CONFIG_PATH): + shutil.copyfile(SAMPLE_FILE_PATH, DEFAULT_CONFIG_PATH) + + +def init(file_path: str = DEFAULT_CONFIG_PATH): + create_copy(file_path) + return physical_ports.get_devices(read(file_path)) + + +def init_profile(profile_name: str): + file_path = get_profile_path(profile_name) + create_copy(file_path) + return physical_ports.get_devices(read(file_path)) + + +def import_level(level: str = "w1"): + if level == "w1": + file_path = os.path.join(os.path.dirname(__file__), "../config/workstation/level-1.toml") + elif level == "w2": + file_path = os.path.join(os.path.dirname(__file__), "../config/workstation/level-2.toml") + elif level == "s1": + file_path = os.path.join(os.path.dirname(__file__), "../config/server/level-1.toml") + elif level == "s2": + file_path = os.path.join(os.path.dirname(__file__), "../config/server/level-2.toml") + + create_copy(file_path, TEMP_FILE_PATH) + return physical_ports.get_devices(read(file_path)) + + def update_toml_obj(toml_obj: tomlkit.items.Item, config: dict): # Recursively update the toml object with the config dict print(config) @@ -48,27 +105,3 @@ def update_toml_obj(toml_obj: tomlkit.items.Item, config: dict): toml_obj[key][i] = value[i] else: toml_obj[key] = value - - -def init(file_path: str = None): - global FILE_PATH, TEMP_FILE_PATH - - if file_path is None: - file_path = os.path.join(os.path.dirname(__file__), "../config/sampleconfig.toml") - - FILE_PATH = file_path - TEMP_FILE_PATH = FILE_PATH + ".tmp" - create_copy() - return physical_ports.get_devices(read()) - -def import_level(level: str = "w1"): - if level == "w1": - file_path = os.path.join(os.path.dirname(__file__), "../config/workstation/level-1.toml") - elif level == "w2": - file_path = os.path.join(os.path.dirname(__file__), "../config/workstation/level-2.toml") - elif level == "s1": - file_path = os.path.join(os.path.dirname(__file__), "../config/server/level-1.toml") - elif level == "s2": - file_path = os.path.join(os.path.dirname(__file__), "../config/server/level-2.toml") - - return init(file_path) diff --git a/harden/script.py b/harden/script.py index fcdf921..df5c912 100644 --- a/harden/script.py +++ b/harden/script.py @@ -1,4 +1,5 @@ import subprocess +import shlex from harden import config_file, physical_ports, file_systems\ , process_hardening, apparmor, gdm, time_sync, firewall\ , network, ssh, privilege_escalation @@ -33,7 +34,9 @@ def save(file_path: str, backup: bool = False): def run(backup: bool = False): save("hardening_script.sh", backup) - subprocess.Popen(["x-terminal-emulator", "-e", "'bash hardening_script.sh'"]) + subprocess.Popen( + shlex.split("""x-terminal-emulator -e "bash -c 'sudo bash hardening_script.sh; read -p \"Press enter to continue\"'" """) + ) if __name__ == "__main__": config_file.init() From 0bfe0d1316ffdac1961ced3d27fa106e024706ef Mon Sep 17 00:00:00 2001 From: Abhishek M J Date: Wed, 20 Dec 2023 16:37:16 +0530 Subject: [PATCH 10/11] Update config backend for profiles --- config/server/level-1.toml | 5 +++-- config/server/level-2.toml | 5 +++-- config/workstation/level-1.toml | 2 +- config/workstation/level-2.toml | 5 +++-- harden/config_file.py | 2 +- harden/physical_ports.py | 5 ++++- main.py | 1 + 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/config/server/level-1.toml b/config/server/level-1.toml index 3497389..f991c45 100644 --- a/config/server/level-1.toml +++ b/config/server/level-1.toml @@ -103,7 +103,7 @@ disable_root_login = true disable_host_based_auth = true disable_permit_empty_passwords = true disable_permit_user_env = true -enable_ingore_rhosts = true +enable_ignore_rhosts = true disable_x11_forwarding = false enable_strong_ciphers = true enable_strong_mac_algorithms = true @@ -117,8 +117,9 @@ enable_max_sessions = true max_sessions = 10 enable_login_grace_time = true login_grace_time = 60 # in seconds -enable_client_alive = true +enable_client_alive_interval = true client_alive_interval = 300 # in seconds +enable_client_alive_count_max = true client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation diff --git a/config/server/level-2.toml b/config/server/level-2.toml index 9769535..3026307 100644 --- a/config/server/level-2.toml +++ b/config/server/level-2.toml @@ -102,7 +102,7 @@ disable_root_login = true disable_host_based_auth = true disable_permit_empty_passwords = true disable_permit_user_env = true -enable_ingore_rhosts = true +enable_ignore_rhosts = true disable_x11_forwarding = true enable_strong_ciphers = true enable_strong_mac_algorithms = true @@ -116,8 +116,9 @@ enable_max_sessions = true max_sessions = 10 enable_login_grace_time = true login_grace_time = 60 # in seconds -enable_client_alive = true +enable_client_alive_interval = true client_alive_interval = 300 # in seconds +enable_client_alive_count_max = true client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation diff --git a/config/workstation/level-1.toml b/config/workstation/level-1.toml index 606dc8b..11243f8 100644 --- a/config/workstation/level-1.toml +++ b/config/workstation/level-1.toml @@ -103,7 +103,7 @@ disable_root_login = true disable_host_based_auth = true disable_permit_empty_passwords = true disable_permit_user_env = true -enable_ingore_rhosts = true +enable_ignore_rhosts = true disable_x11_forwarding = true enable_strong_ciphers = true enable_strong_mac_algorithms = true diff --git a/config/workstation/level-2.toml b/config/workstation/level-2.toml index edf5364..783f8af 100644 --- a/config/workstation/level-2.toml +++ b/config/workstation/level-2.toml @@ -102,7 +102,7 @@ disable_root_login = true disable_host_based_auth = true disable_permit_empty_passwords = true disable_permit_user_env = true -enable_ingore_rhosts = true +enable_ignore_rhosts = true disable_x11_forwarding = true enable_strong_ciphers = true enable_strong_mac_algorithms = true @@ -116,8 +116,9 @@ enable_max_sessions = true max_sessions = 10 enable_login_grace_time = true login_grace_time = 60 # in seconds -enable_client_alive = true +enable_client_alive_interval = true client_alive_interval = 300 # in seconds +enable_client_alive_count_max = true client_alive_count_max = 3 [privilege_escalation] # Privilege Escalation diff --git a/harden/config_file.py b/harden/config_file.py index 9ad8bce..4f2e5e2 100644 --- a/harden/config_file.py +++ b/harden/config_file.py @@ -71,6 +71,7 @@ def init(file_path: str = DEFAULT_CONFIG_PATH): def init_profile(profile_name: str): file_path = get_profile_path(profile_name) + shutil.copyfile(DEFAULT_CONFIG_PATH, file_path) create_copy(file_path) return physical_ports.get_devices(read(file_path)) @@ -91,7 +92,6 @@ def import_level(level: str = "w1"): def update_toml_obj(toml_obj: tomlkit.items.Item, config: dict): # Recursively update the toml object with the config dict - print(config) for key, value in config.items(): if isinstance(value, dict): update_toml_obj(value, toml_obj[key]) diff --git a/harden/physical_ports.py b/harden/physical_ports.py index cc04e0e..8a62fba 100644 --- a/harden/physical_ports.py +++ b/harden/physical_ports.py @@ -39,7 +39,10 @@ def get_devices(all_config): else: ports[port_id] = {"id": port_id, "name": device_name, "allow": True} - config.update({"device-rules": list(devices.values()), "port-rules": list(ports.values())}) + new_config = all_config.unwrap() + new_config["physical-ports"]["device-rules"] = list(devices.values()) + new_config["physical-ports"]["port-rules"] = list(ports.values()) + config_file.update_toml_obj(all_config, new_config) return all_config diff --git a/main.py b/main.py index fa98d61..319344e 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ class MainWindow(QMainWindow): theme_signal = pyqtSignal(bool) def __init__(self): super().__init__() + config_file.init_config_dir() self.config = config_file.init() self.tooltip = tooltip_file.read() self.init_ui() From de9a72d6b7e1375d61c599c7ea74fe61c490d660 Mon Sep 17 00:00:00 2001 From: Abhishek M J Date: Wed, 20 Dec 2023 16:52:16 +0530 Subject: [PATCH 11/11] Fix profiles --- harden/config_file.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/harden/config_file.py b/harden/config_file.py index 4f2e5e2..8b45432 100644 --- a/harden/config_file.py +++ b/harden/config_file.py @@ -43,9 +43,11 @@ def get_profiles(): init_config_dir() return [] - profiles = os.listdir(PROFILE_DIR) - for i in range(len(profiles)): - profiles[i] = profiles[i].replace("_config.toml", "") + all_files = os.listdir(PROFILE_DIR) + profiles = [] + for file in all_files: + if file.endswith("_config.toml"): + profiles.append(file.replace("_config.toml", "")) return profiles