54d348c99f
This commit introduces several significant enhancements to the way commands are executed and logged in the application. The changes are primarily focused on improving the robustness, reliability, and transparency of the command execution process, as well as enhancing the quality and usefulness of the log output. 1. Command Execution Enhancements: The use_sudo method has been refactored to handle commands that do not return any output. Previously, the method was designed to capture and return the output of the command being executed. However, some commands (such as modprobe) do not return any output, which caused issues with the previous implementation. The method now checks the exit status of the command to determine whether it was successful or not, and returns a success or failure message accordingly. This change improves the robustness of the command execution process and ensures that it can handle a wider range of commands. 2. Error Handling Improvements: The use_sudo method now includes more comprehensive error handling. If a command fails to execute within a specified timeout period, an error message is logged and the method returns a failure message. Additionally, if a command fails to execute for any other reason, the method logs the error and returns a failure message with the command's exit status. These changes make it easier to identify and troubleshoot issues with command execution. 3. Logging Enhancements: The logging mechanism has been enhanced to provide more detailed and useful information. The use_sudo method now logs the command being executed and its outcome (success or failure). If a command fails, the method logs the command's exit status. These changes improve the transparency of the command execution process and make it easier to identify and troubleshoot issues. 4. Code Refactoring: Several methods have been refactored for improved readability and maintainability. The use_sudo method has been refactored to reduce its complexity and improve its readability. The first_run_setup method has been refactored to ensure that the main interface name and the dummy interface name are properly passed to the setup_traffic_mirroring method. 5. Bug Fixes: A bug in the create_dummy_interface method that caused it to return an array of Alert objects instead of the dummy interface name has been fixed. The method now correctly returns the dummy interface name. These changes represent a significant improvement to the command execution and logging mechanisms in the application, and lay the groundwork for further enhancements in the future.
214 lines
7.1 KiB
Ruby
214 lines
7.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'English'
|
|
require 'securerandom'
|
|
require 'digest'
|
|
require 'base64'
|
|
require 'openssl'
|
|
require 'pty'
|
|
require 'expect'
|
|
|
|
# Utiltiies Module
|
|
module Utilities # rubocop:disable Metrics/ModuleLength
|
|
# Converts speed from Gbps to Mbps if necessary
|
|
def convert_speed_to_mbps(speed)
|
|
return nil unless speed.is_a?(String) && speed.downcase.match?(/\A\d+(gbps|mbps)\z/i)
|
|
|
|
# Extract the numeric part and the unit from the speed
|
|
numeric_speed, unit = speed.downcase.match(/(\d+)(gbps|mbps)/i).captures
|
|
|
|
# Convert the numeric part to an integer
|
|
numeric_speed = numeric_speed.to_i
|
|
|
|
# If the unit is 'gbps', multiply the numeric part by 1000
|
|
numeric_speed *= 1000 if unit == 'gbps'
|
|
|
|
numeric_speed
|
|
end
|
|
|
|
# Converts an array of services into a hash
|
|
def services_to_hash(services)
|
|
services_hash = {}
|
|
services.each { |service| services_hash[service] = true }
|
|
services_hash
|
|
end
|
|
|
|
# Calculates total bandwidth from uplink and downlink speeds
|
|
def calculate_total_bandwidth(uplink_speed, downlink_speed)
|
|
uplink_speed + downlink_speed
|
|
end
|
|
|
|
def generate_key
|
|
Base64.encode64(SecureRandom.bytes(32)).chomp
|
|
end
|
|
|
|
def encrypt_string_chacha20(data, key)
|
|
return nil if data.nil? || key.nil?
|
|
|
|
cipher = OpenSSL::Cipher.new('chacha20')
|
|
cipher.encrypt
|
|
cipher.key = Base64.decode64(key) # Decode the key from Base64
|
|
encrypted_data = cipher.update(data) + cipher.final
|
|
|
|
Base64.encode64(encrypted_data).chomp
|
|
rescue OpenSSL::Cipher::CipherError => e
|
|
@loggman.log_error("Failed to encrypt data: #{e.message}")
|
|
nil
|
|
end
|
|
|
|
def decrypt_string_chacha20(encrypted_data, key) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
return nil if encrypted_data.nil? || key.nil?
|
|
|
|
cipher = OpenSSL::Cipher.new('chacha20')
|
|
cipher.decrypt
|
|
cipher.key = Base64.decode64(key) # Decode the key from Base64
|
|
decrypted_data = cipher.update(Base64.decode64(encrypted_data)) + cipher.final
|
|
|
|
# Check if the decrypted data is valid ASCII
|
|
decrypted_data.force_encoding('UTF-8')
|
|
if decrypted_data.valid_encoding?
|
|
decrypted_data
|
|
else
|
|
@loggman.log_error("Decrypted data is not valid ASCII: #{decrypted_data.inspect}")
|
|
nil
|
|
end
|
|
rescue OpenSSL::Cipher::CipherError => e
|
|
@loggman.log_error("Failed to decrypt data: #{e.message}")
|
|
nil
|
|
end
|
|
|
|
def ask_for_sudo(logger) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
@loggman = logger
|
|
@loggman.log_info('Asking for sudo and explaining why...')
|
|
lines = [
|
|
'We require sudo permissions to complete certain steps.',
|
|
'Granting a program sudo access is a significant decision.',
|
|
'We treat your sudo password with the utmost care.',
|
|
'We handle it "As a Bomb".',
|
|
'',
|
|
'As soon as we receive your sudo password, it is encrypted.',
|
|
'The unencrypted password is then wiped from the system.',
|
|
'This includes the sudo cache.',
|
|
'When we need to use your sudo password, we decrypt it.',
|
|
'We use it, and then immediately wipe it again.',
|
|
'We only ever store the encrypted version of your password.',
|
|
'We delete even that as soon as we finish the operations.',
|
|
'However, even with these precautions, there is always a risk.',
|
|
'If an attacker were to gain access to this program,',
|
|
'they could potentially decrypt your password.',
|
|
'Therefore, you should only enter your sudo password',
|
|
'if you fully understand and accept these risks.',
|
|
'',
|
|
'',
|
|
'Please enter your sudo password ONLY if you understand',
|
|
'and accept the risks described above: '
|
|
]
|
|
Curses.clear
|
|
lines.each_with_index do |line, index|
|
|
Curses.setpos(index, 0) # Move the cursor to the beginning of the next line
|
|
Curses.addstr(line)
|
|
end
|
|
# use the dynamic curses input gem in secure mode to collect the sudo password
|
|
sudo_password = DCI.catch_input(false)
|
|
@loggman.log_info('Sudo password received. (This log entry will be removed)')
|
|
@loggman.log_info("Entered sudo password: #{sudo_password}")
|
|
|
|
# Generate a new secret key
|
|
key = SecureRandom.random_bytes(32) # generates a random string of 32 bytes
|
|
|
|
# encode the key for storage
|
|
@secret_key = Base64.encode64(key)
|
|
|
|
# Encrypt the sudo password right away and store it in an environment variable
|
|
encrypted_sudo_password = encrypt_string_chacha20(sudo_password, @secret_key)
|
|
@loggman.log_info("Encrypted sudo password: #{encrypted_sudo_password}")
|
|
ENV['SPW'] = encrypted_sudo_password
|
|
ENV['SDSECRET_KEY'] = @secret_key
|
|
|
|
# Clear the unencrypted sudo password from memory
|
|
sudo_password.replace(' ' * sudo_password.length)
|
|
end
|
|
|
|
def test_sudo
|
|
# Run a simple ls command with sudo privileges to test
|
|
use_sudo('ls')
|
|
|
|
true
|
|
rescue PTY::ChildExited
|
|
false
|
|
end
|
|
|
|
def deescalate_sudo
|
|
# Retrieve the encrypted sudo password from the environment variable
|
|
encrypted_sudo_password = ENV['SPW']
|
|
|
|
# Retrieve the secret key from the environment variable
|
|
@secret_key = ENV['SDSECRET_KEY']
|
|
|
|
# Decrypt the sudo password
|
|
sudo_password = decrypt_string_chacha20(encrypted_sudo_password, @secret_key)
|
|
|
|
# Invalidate the user's cached credentials
|
|
PTY.spawn('sudo -S -k') do |r, w, _pid|
|
|
w.sync = true
|
|
r.expect(/password/) { w.puts sudo_password }
|
|
end
|
|
|
|
# Clear the sudo password from memory
|
|
sudo_password.replace(' ' * sudo_password.length)
|
|
|
|
# Remove the encrypted sudo password and the secret key from the environment variables
|
|
ENV.delete('SPW')
|
|
ENV.delete('SECRET_KEY')
|
|
end
|
|
|
|
def use_sudo(command) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
@secret_key = ENV['SDSECRET_KEY']
|
|
# Retrieve the encrypted sudo password from the environment variable
|
|
encrypted_sudo_password = ENV['SPW']
|
|
|
|
# Decrypt the sudo password
|
|
sudo_password = decrypt_string_chacha20(encrypted_sudo_password, @secret_key)
|
|
|
|
# this is only here for debugging
|
|
@loggman.log_info("Decrypted sudo password: #{sudo_password}")
|
|
|
|
# Log the command
|
|
@loggman.log_info("Running command: #{command}")
|
|
|
|
# Use the sudo password to run the command
|
|
exit_status = nil
|
|
begin
|
|
Timeout.timeout(60) do
|
|
PTY.spawn("sudo -S #{command} 2>&1") do |r, w, pid|
|
|
w.sync = true
|
|
r.expect(/password/) { w.puts sudo_password }
|
|
while IO.select([r], nil, nil, 0.1)
|
|
output = r.read_nonblock(1000)
|
|
output.each_line { |line| @loggman.log_info("Command output: #{line.strip}") }
|
|
end
|
|
begin
|
|
Process.wait(pid)
|
|
exit_status = $CHILD_STATUS.exitstatus
|
|
ensure
|
|
# Clear the sudo password from memory
|
|
sudo_password.replace(' ' * sudo_password.length)
|
|
end
|
|
end
|
|
end
|
|
rescue Timeout::Error
|
|
@loggman.log_error("Command '#{command}' timed out")
|
|
rescue Errno::EIO
|
|
# This error is expected when the process has finished
|
|
end
|
|
|
|
if exit_status&.zero?
|
|
@loggman.log_info("Command '#{command}' completed successfully")
|
|
else
|
|
@loggman.log_error("Command '#{command}' failed with exit status #{exit_status}")
|
|
end
|
|
|
|
exit_status
|
|
end
|
|
end
|