Archive for the ‘Security’ Category

Linux Bash Logging log everything. Prevent user from delete his history and keep record of every command User ever Run

Tuesday, March 17th, 2026

make_bash_history_permanent-how-to-keep-every-user-command-forever-prevent-users-from-deleting-their-bash-history-on-linux

Whether you're managing servers, writing scripts, or troubleshooting complex systems, one of the most valuable tools at your disposal is your command history. But the default Bash history has serious limitations: it’s easy to lose, doesn't timestamp by default, and doesn't log everything in real time.

What if you could keep a permanent, timestamped, real-time log of every command you ever run in Bash?

Good news: you can.

In this guide, we’ll walk through how to set up robust, automatic Bash logging to track every command you type—across sessions, with full timestamps, and even with user and host information. Ideal for system administrators, developers, auditors, or anyone who wants to maintain a clear, searchable audit trail.

Why Bash Logging Persistence So Important ?

Before we dive into the how, let's understand the why:

  • Accountability – Know exactly what commands were run, by whom, and when.
  • Auditability – Great for security reviews or compliance requirements.
  • Troubleshooting – Trace back actions that caused issues.
  • Documentation – Reuse commands or share with teammates.
  • Forensics – Investigate suspicious activity.

How Bash History Behaves  ( By Default )

Without any config everyone knows , Bash uses a file called ~/.bash_history in $HOME to save command history.

What is tricky here:

  • .bash_history not written to immediately – only when the session exits.
  • It can be overwritten by other sessions.
  • It lacks timestamps unless explicitly configured.
  • It doesn’t log failed attempts or commands from other users.

In this short article I'll show you one of the ways on how to make .bash_history keeps the record for you even though some user tries to hide tihngs by running the commands and exiting the shell abnormally by killing it with the well known command by hackers and sysadmin gurus:


$ kill -9 $$

The command forces the user you have logged into to kill the process of the bash (-bash). 

Here is how.

Enable Advanced Bash Logging

1. Enable Timestamps in History

Add this line to your ~/.bashrc or ~/.bash_profile:

export HISTTIMEFORMAT="%F %T "

This formats the date/time as YYYY-MM-DD HH:MM:SS.

After modifying the file, run:

source ~/.bashrc

Now, run:

history

And you’ll see timestamps next to your commands.

2. Increase History Size

The default history size is often too small. Let’s increase it:

export HISTSIZE=100000

export HISTFILESIZE=200000


Add these to ~/.bashrc as well.

3. Log Commands Immediately (Across Sessions)

By default, Bash only writes history when the shell exits. To log commands in real time, add the following to ~/.bashrc:

# Append to the history file, don't overwrite it

shopt -s histappend

# Immediately append command to history file after execution

PROMPT_COMMAND='history -a; history -n'

Explanation:

  • history -a: Append current session's command to ~/.bash_history
  • history -n: Read any new lines from the file (from other sessions)

4. Log All Commands to a Separate File (for each User)

To keep a separate, detailed log, you can use the trap command in combination with logger, or write to a custom file.

Add this to your ~/.bashrc:

LOG_FILE="$HOME/.bash_command_log"

trap 'echo "$(date "+%F %T") | $(whoami)@$(hostname) | $(pwd) | $BASH_COMMAND" >> "$LOG_FILE"' DEBUG

This logs every command as for example:

2025-10-10 14:25:02 | master_app@server01 | /var/www | systemctl restart nginx

This file can grow large over time – consider rotating it regularly with logrotate or similar tools.
To prevent the file 100% from being modified by the user itself you can make the log file  immutable with command

# chattr +i $HOME/.bash_command_log


5. Guarantee log security, Make copy of Logs to prevent hackers to modify them

If logging for audit/security purposes:

  • Store logs in append-only files (chattr +a logfile on ext4 FS)
  • store files with rsyslog service (see below)
  • Use remote logging (e.g., send via logger to syslog  / rsyslog or any other centralized logging service) / logcollector etc.
  • Monitor for tampering or suspicious gaps

6. Store file with rsyslog service

Create the file and set it proper permissions

# touch /var/log/bash_audit.log
# chmod 600 /var/log/bash_audit.log
# chown root:root /var/log/bash_audit.log

# vim /etc/rsyslog.d/bash_audit.conf

Add:

if $programname == 'bash_audit' then /var/log/bash_audit.log
& stop

# systemctl restart rsyslog


To later verify it works fine

# tail -f /var/log/bash_audit.log
# journalctl -t bash_audit

 

6. Add Global Bash Logging for All Users

Assuming that the bash_audit set program / name tag is already done as in step 5.
To apply logging system-wide, Edit /etc/profile /etc/bash_profile or /etc/bash.bashrc and include the same trap cmd and logging is ready. Ensure:

  • The log file is writable by users (or add users to a group that can append to file) or modify the command to use sudo logger for centralized syslog.
  • You test it carefully before deploying to all users.

     

     

     

    An improved wide user version of trap command would be something like this

# Bash command logging (readable layer)

trap 'CMD=$(history 1 | sed "s/^[ ]*[0-9]\+[ ]*//");
MSG="$(date "+%F %T") | $(whoami)@$(hostname) | $(pwd) | $CMD";

/usr/bin/logger -t bash_audit "$MSG"
' DEBUG

Make these two env variables read only for additional hardening 

readonly PROMPT_COMMAND
readonly HISTFILE

Note that you will need to edit passwordless login for sudo to logger

  • Setup auditd to make file read only

# apt install auditd audispd-plugins –yes

  • Test it with auditctl

# auditctl -a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k cmdlog
# auditctl -a always,exit -F arch=b32 -S execve -F auid>=1000 -F auid!=4294967295 -k cmdlog

  • Make rules permanent via cmdlog.rules

# vim /etc/audit/rules.d/cmdlog.rules

-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k cmdlog
-a always,exit -F arch=b32 -S execve -F auid>=1000 -F auid!=4294967295 -k cmdlog

  • Load and lock audit rules

# augenrules –load
# auditctl -e 2

  • Check audit logs

# ausearch -k cmdlog -i
exe="/usr/bin/ls" argc=1 a0="ls"

7. Rotate Log Files Automatically with logrotate

Create a logrotate config like /etc/logrotate.d/bash_command_log:

/home/*/.bash_command_log {
daily
rotate 7
compress
missingok
notifempty
}

/var/log/bash_audit.log {
daily
rotate 7
compress
missingok
notifempty
}


This keeps logs for 7 days and compresses old ones.

8. Test Every command Logging is permanenty stored

After setting bash logging up up:

  1. Open a new terminal client with SSH session
  2. Run a few commands
  3. Check ~/.bash_command_log (or your alternative configured log location)

You should see a real-time record of every command executed.

Use tools like grep, awk, or fzf Command fuzzy finder to search through your command log efficiently. Example:

grep apt ~/.bash_command_log

You can further automate it and deploy it to multiple servers with Ansible or some shell scripting.
If you need it Ask me how to automate it?
Ask me how to automate it with Ansible or a shell script.

Wrapping it Up

With just a few lines in Bash config, basic history feature becomes a persistent, and timestamped static record  that’s invaluable for system admins, developers, and security teams.

Summary Checklist

  • Enable HISTTIMEFORMAT
  • Increase history size
  • Append history in real time
  • Log every command with trap DEBUG
  • Optionally send to rsyslog / syslogd / systemd-journald or other central log server (Fluentd / ELK Stack / Graylog)
  • Rotate logs with logrotate

How to Fix Windows Update When It Says “Up to Date” But Updates Are Missing

Monday, March 2nd, 2026

windows-os-update-up-to-date-but-OS_update-release-lacking-behind-fix-Windows-shows-updated-but-it-is-not

Knowing your system isn’t fully updated as OS BUILD Release does not match the latest one it has to  but  still Windows Update insists everything is “green good and  “Up to date.” is really weird and frustrating stuff Windows user can experience. It makes it even worser if you are like me and your computer is in a large corporate domain that is using Azure (Office 365) services for Auth.

If some updates fail silently or don’t install properly, your notebook / PC may be missing important security patches, Video / Sound Driver / Chipset driver fixes, or feature improvements etc, and with time it can lead due to Windows domain applied policies to left over your computer be considered Unsafe or Broken even dis-joined from the Domain.
 

Why Windows Update Says Up to Date but Update Are missing Happens ?

There might be mutiple scenarios but Common causes include:

  • Corrupted update cache
  • Interrupted installations (PC got hard shut down electricity power outage or
    laptop battery has discharged during update)
  • Broken Windows services (due to)
  • System file corruption (cause of viruses / malware or during mess left over of multiple windows updates over years)
  • Registry conflicts (Windows registry conflicts due to installed PC apps etc.)
  • Failed cumulative updates

Windows may mark updates as “processed” even if installation didn’t complete correctly.
Identifying Missing or Broken Windows updates is really hard sometimes.

Usually to capture it you will have to:
Check the Windows OS Build Release

from: Settings -> System -> About

windows-11-settings-system-about-OS-BUILD-release-screenshotpng

In this guide, will walk through proven methods to fix Windows Update when it’s stuck or falsely reporting success.

1. Try PC Restart First

Before diving into advanced fixes:

  • Restart PC.
  • Go Check Settings → Windows Update → Check for updates again.

Sometimes updates are downloaded but waiting for a reboot to complete installation and thus this oddity is observed.

2. Run the Built-In Windows Update Troubleshooter

Both Windows 10 and Windows 11 include a built-in repair tool (that is starting to get Legacy nowadays but still sometimes can help)

Steps:

  1. Open Settings
  2. Go to System → Troubleshoot → Other troubleshooters
  3. Find Windows Update
  4. Click Run

Let it complete the scan and apply any recommended fixes. Most time this won’t solve it but as it is easy to try out give it a try.

3. Manually Reset Windows Update Components

If Windows still erroneously thinks everything is installed but something is broken internally, resetting the update components often solves the problem.

CleanUp SoftwareDistribution update cache folder is perhaps Most Effective FIX

Cleaning the C:\Windows\SoftwareDistribution folder is actually one of the most effective fixes when Windows refuses to install updates but claims everything is up to date.

C:\Windows\SoftwareDistribution

This is where Windows temporarily stores:

  • Downloaded update files
  • Update installation logs
  • Temporary metadata
  • Cached update database

If this cache becomes corrupted, Windows Update may:

  • Fail silently
  • Not detect new updates
  • Show “Up to date” incorrectly
  • Get stuck at 0% or 100%

This method works in both Windows 10 and Windows 11.

What Happens When You Delete SoftwareDistribution?

Deleting (or renaming) the folder:

  • Does NOT delete installed updates
  • Does NOT break Windows
  • Forces Windows to rebuild the update cache
  • Forces a fresh update scan

It’s completely safe if you do it correct.

Recommended Method (Play Safe)

N!B! Do NOT delete the folder while update services are running.

Step 1: Stop Windows Update Services

Open Command Prompt as Administrator and run:
 

net stop wuauserv
net stop bits
net stop cryptSvc
net stop msiserver

Wait until all services stop successfully.

Step 2: Rename the Update Folder (Safer Than Deleting)

Rename Update Folders

move C:\Windows\SoftwareDistribution SoftwareDistribution.old

Enter in Windows Safe Mode (to enter it Press SHIFT and choose Restart)

Go to:

C:\Windows\SoftwareDistribution

Rename it to:

C:\Windows\SoftwareDistribution.old

If Windows refuses to move out, make sure services are stopped.

To do it via Safe Mode with Command Prompt only

move c:\Windows\SoftwareDistribution C:\Windows\SoftwareDistribution.old

Step 3: Restart Services

Back in Command Prompt:

net start wuauserv
net start bits
net start cryptSvc
net start msiserver

Restart Computer.
 

4. Use the Microsoft Update Catalog to Manually download recent applied
Update

Sometimes a specific update fails repeatedly but Windows doesn’t clearly report it.

You can manually download it from:

  • Microsoft Update Catalog

How to manually Instlal KB* Win update:

  1. Find the KB number (for example: KB5030219)
  2. Search for it in the catalog
  3. Download the version matching your system (x64, ARM64, etc.)
  4. Install manually

This bypasses Windows Update’s automatic system.

5. Use the Windows Installation Assistant

If feature updates (like 22H2 → 23H2) are not appearing, use:

  • Windows 11 Installation Assistant
  • Windows 10 Update Assistant

These tools force a full system upgrade while keeping files and apps intact.

6. Check for Corrupted System Files

Corrupted system files can prevent updates from applying properly.

Open Command Prompt as Administrator and run:

C:\Windows>  sfc /scannow

Then run:

C:\Windows> DISM /Online /Cleanup-Image /RestoreHealth

After both scans complete, restart and try updating again.

7. Make Sure You’re Not Paused or you are on a Metered connection

Windows may appear updated if:

  • Updates are paused
  • Your connection is set as metered
  • You’re on a managed/work PC with update policies

Check:

  • Settings → Windows Update → Advanced options

8. Check Your Windows Version Manually

Press Win + R, type:

winver

Compare your version with the latest available on Microsoft’s official release page
https://learn.microsoft.com/en-en/windows/release-health/windows11-release-information
to confirm whether you’re truly up to date.

9. Update your Video / Audio / Motherboard Chipsets and peripheral drivers to latest

Depending on the laptop brand or PC, Check for latest available install drivers from the Internet and apply it to PC.
Dell / HP and ASUS / ACER / MSI
Usually has their dedicated software that can do that quickly, i.e. as i'm using currently Dell notebook. There you can use Dell Comamnd Update / Dell SupportAssistant to do so
 

10. Move catroot folder (to clean up Windows Update package signatures)

What is catroot2 ?

The catroot2 folder is used by Microsoft Windows to store:

  • Windows Update package signatures
  • Cryptographic catalog files (.cat files)
  • Data used by the Cryptographic Services component
  • Information needed to validate and install updates
  • It plays a critical role in verifying update integrity.

move C:\Windows\System32\catroot2 catroot2.old

is used as a repair step for Windows Update issues because it resets the Catroot2 folder, which stores important update-related data.

11. Perform an In-Place Repair Upgrade (Last Resort)

If nothing works:

  1. Download the latest Windows ISO (Windows Installation Assistant)
  2. Mount it
  3. Run setup.exe
  4. Choose Keep personal files and apps

This reinstalls Windows without deleting your data and fixes deeply broken update components.

12. If none of these helps check Windows Logs for a clue

If you want to go even deeper, check Event Viewer logs under:

Windows Logs → Setup

That will show detailed update errors and will helpfully give you the clue on how to fix it.

Summary / close up

If Windows says “Up to date” but you suspect missing updates, don’t ignore it, as soon your OS will either become messed or you will miss critical Performance and Performance improvements / Stability Features. Even if PC continues work relatively stable the missing Security patches would be critical, and the computer exposure to the internet lefts you as an easy victim for your computer to be hacked or infected by some kind of encryption / ransomware worm etc. In most cases, the updates did not apply due to easy solvable issue and simple reset update components, a clean up of Update cache or manually installing the update solves the problem and WIndows gets back to the wanted OS update release. If this does not happen however you should check the system for Main system corrupted files 

 

How to Set Up SSH Two-Factor Authentication (2FA) on Linux Without Google Authenticator with OATH Toolkit

Wednesday, November 5th, 2025

install-2-factor-free-authentication-google-authentication-alternative-with-oath-toolkit-linux-logo

Most tutorials online on how to secure your SSH server with a 2 Factor Authentication 2FA will tell you to use Google Authenticator to secure SSH logins.

But what if you don’t want to depend on Google software – maybe for privacy, security, or ideological reasons ?

Luckily, you have a choice thanks to free oath toolkit.
The free and self-hosted alternative: OATH Toolkit has its own PAM module  libpam-oath to make the 2FA work  the openssh server.

OATH-Toolkit is a free software toolkit for (OTP) One-Time Password authentication using HOTP/TOTP algorithms. The software ships a small set of command line utilities covering most OTP operation related tasks.

In this guide, I’ll show you how to implement 2-Factor Authentication (TOTP) for SSH on any Linux system using OATH Toolkit, compatible with privacy-friendly authenticator apps like FreeOTP, Aegis, or and OTP.

It is worthy to check out OATH Toolkit author original post here, that will give you a bit of more insight on the tool.

1. Install the Required Packages

For Debian / Ubuntu systems:

# apt update
# apt install libpam-oath oathtool qrencode
...

For RHEL / CentOS / AlmaLinux:
 

# dnf install pam_oath oathtool

The oathtool command lets you test or generate one-time passwords (OTPs) directly from the command line.

2. Create a User Secret File

libpam-oath uses a file to store each user’s secret key (shared between your server and your phone app).

By default, it reads from:

/etc/users.oath

Let’s create it securely and set proper permissions to secure it:
 

# touch /etc/users.oath
# chmod 600 /etc/users.oath

Now, generate a new secret key for your user (replace hipo with your actual username):
 

# head -10 /dev/urandom | sha1sum | cut -c1-32

This generates a random 32-character key.
Example:

9b0e4e9fdf33cce9c76431dc8e7369fe

Add this to /etc/users.oath in the following format:

HOTP/T30 hipo - 9b0e4e9fdf33cce9c76431dc8e7369fe

HOTP/T30 means Time-based OTP with 30-second validity (standard TOTP).

Replace hipo with the Linux username you want to protect.

3. Add the Key to Your Authenticator App

Now we need to add that secret to your preferred authenticator app.

You can create a TOTP URI manually (to generate a QR code):

$ echo "otpauth://totp/hipo@jericho?secret=\
$(echo 9b0e4e9fdf33cce9c76431dc8e7369fe \
| xxd -r -p | base32)"

You can paste this URI into a QR code generator (e.g., https://qr-code-generator.com) and scan it using FreeOTP , Aegis, or any open TOTP app.
The FreeOTP Free Ap is my preferred App to use, you can install it via Apple AppStore or Google Play Store.

Alternatively, enter the Base32-encoded secret manually into your app:

# echo 9b0e4e9fdf33cce9c76431dc8e7369fe | xxd -r -p | base32

You can also use qrencode nice nifty tool to generate out of your TOTP code in ASCII mode and scan it with your Phone FreeOTP / Aegis App and add make it ready for use:

# qrencode –type=ANSIUTF8 otpauth://totp/hipo@jericho?secret=$( oathtool –verbose –totp 9b0e4e9fdf33cce9c76431dc8e7369fe –digits=6 -w 1 | grep Base32 | cut -d ' ' -f 3 )\&digits=6\&issuer=pc-freak.net\&period=30

qrencode-generation-of-scannable-QR-code-for-freeotp-or-other-TOTP-auth

qrencode will generate the code. We set the type to ANSI-UTF8 terminal graphics so you can generate this in an ssh login. It can also generate other formats if you were to incorporate this into a web interface. See the man page for qrencode for more options.
The rest of the line is the being encoded into the QR code, and is a URL of the type otpauth, with time based one-time passwords (totp). The user is “hipo@jericho“, though PAM will ignore the @jericho if you are not joined to a domain (I have not tested this with domains yet).

The parameters follow the ‘?‘, and are separated by ‘&‘.

otpauth uses a base32 hash of the secret password you created earlier. oathtool will generate the appropriate hash inside the block:

 $( oathtool –verbose –totp 9b0e4e9fdf33cce9c76431dc8e7369fe | grep Base32 | cut -d ' ' -f 3 )

We put the secret from earlier, and search for “Base32”. This line will contain the Base32 hash that we need from the output:

Hex secret: 9b0e4e9fdf33cce9c76431dc8e7369fe
Base32 secret: E24ABZ2CTW3CH3YIN5HZ2RXP
Digits: 6
Window size: 0
Step size (seconds): 30
Start time: 1970-01-01 00:00:00 UTC (0)
Current time: 2022-03-03 00:09:08 UTC (1646266148)
Counter: 0x3455592 (54875538)

368784 
From there we cut out the third field, “E24ABZ2CTW3CH3YIN5HZ2RXP“, and place it in the line.

Next, we set the number of digits for the codes to be 6 digits (valid values are 6, 7, and 8). 6 is sufficient for most people, and easier to remember.

The issuer is optional, but useful to differentiate where the code came from.

We set the time period (in seconds) for how long a code is valid to 30 seconds.

Note that: Google authenticator ignores this and uses 30 seconds whether you like it or not.

4. Configure PAM to Use libpam-oath

Edit the PAM configuration for SSH:

# vim /etc/pam.d/sshd

At the top of the file, add:

auth required pam_oath.so usersfile=/etc/users.oath window=30 digits=6

This tells PAM to check OTP codes against /etc/users.oath.

5. Configure SSH Daemon to Ask for OTP

Edit the SSH daemon configuration file:
 

# vim /etc/ssh/sshd_config

Ensure these lines are set:
 

UsePAM yes
challengeresponseauthentication yes
ChallengeResponseAuthentication yes
AuthenticationMethods publickey keyboard-interactive
##KbdInteractiveAuthentication no
KbdInteractiveAuthentication yes

N.B.! The KbdInteractiveAuthentication yes variable is necessery on OpenSSH servers with version > of version 8.2_ .

In short This setup means:
1. The user must first authenticate with their SSH key (or local / LDAP password),
2. Then enter a valid one-time code generated from TOTP App from their phone.

You can also use  Match  directives to enforce 2FA under certain conditions, but not under others.
For example, if you didn’t want to be bothered with it while you are logging in on your LAN,
but do from any other network, you could add something like:

Match Address 127.0.0.1,10.10.10.0/8,192.168.5.0/24
Authenticationmethods publickey

6. Restart SSH and Test It

Apply your configuration:
 

# systemctl restart ssh

Now, open a new terminal window and try logging in (don’t close your existing one yet, in case you get locked out):

$ ssh hipo@your-server-ip

You should see something like:

Verification code:

Enter the 6-digit code displayed in your FreeOTP (or similar) app.
If it’s correct, you’re logged in! Hooray ! 🙂

7. Test Locally and Secure the Secrets

If you want to test OTPs manually with a base32 encrypted output of hex string:

# oathtool --totp -b \
9b0e4e9fdf33cce9c76431dc8e7369fe

As above might be a bit confusing for starters, i recommend to use below few lines instead:

$ secret_hex="9b0e4e9fdf33cce9c76431dc8e7369fe"
$ secret_base32=$(echo $secret_hex | xxd -r -p | base32)
$ oathtool –totp -b "$secret_base32"
156874

You’ll get the same 6-digit code your authenticator shows – useful for debugging.

If you rerun the oathtool again you will get a difffefrent TOTP code, e.g. :

$ oathtool –totp -b "$secret_base32"
258158


Use this code as a 2FA TOTP auth code together with local user password (2FA + pass pair),  when prompted for a TOTP code, once you entered your user password first.

To not let anyone who has a local account on the system to be able to breach the 2FA additional password protection,
Ensure the secrets file is protected well, i.e.:

# chown root:root /etc/users.oath
# chmod 600 /etc/users.oath

How to Enable 2FA Only for Certain Users

If you want to force OTP only for admins, create a group ssh2fa:

# groupadd ssh2fa
# usermod -aG ssh2fa hipo

Then modify /etc/pam.d/sshd:

auth [success=1 default=ignore] pam_succeed_if.so \
user notingroup ssh2fa
auth required pam_oath.so usersfile=/etc/users.oath \
window=30 digits=6

Only users in ssh2fa will be asked for a one-time code.

Troubleshooting

Problem: SSH rejects OTP
Check /var/log/auth.log or /var/log/secure for more details.
Make sure your phone’s time is in sync (TOTP depends on accurate time).

Problem: Locked out after restart
Always keep one root session open until you confirm login works.

Problem: Everything seems configured fine but still the TOTP is not accepted by remote OpenSSHD.
– Check out the time on the Phone / Device where the TOTP code is generated is properly synched to an Internet Time Server
– Check the computer system clock is properly synchornized to the Internet Time server (via ntpd / chronyd etc.), below is sample:

  • hipo@jeremiah:~$ timedatectl status
                   Local time: Wed 2025-11-05 00:39:17 EET
               Universal time: Tue 2025-11-04 22:39:17 UTC
                     RTC time: Tue 2025-11-04 22:39:17
                    Time zone: Europe/Sofia (EET, +0200)
    System clock synchronized: yes
                  NTP service: n/a
              RTC in local TZ: no

Why Choose libpam-oath?

  • 100% Free Software (GPL)
  • Works completely offline / self-hosted
  • Compatible with any standard TOTP app (FreeOTP, Aegis, andOTP, etc.)
  • Doesn’t depend on Google APIs or cloud services
  • Lightweight (just one PAM module and a text file)

Conclusion

Two-Factor Authentication doesn’t have to rely on Google’s ecosystem.
With OATH Toolkit and libpam-oath, you get a simple, private, and completely open-source way to harden your SSH server against brute-force and stolen-key attacks.

Once configured, even if an attacker somehow steals your SSH key or password, they can’t log in without your phone’s one-time code – making your system dramatically safer.

Digital Vigilance: Practical Cyber Defense for the New Era of All connected dependency on technology

Friday, October 24th, 2025

 

Introduction

There was a time when cybersecurity was mostly about erecting a firewall, installing antivirus software and hoping no one clicked a suspicious link. That era is steadily fading. Today, as more work moves to the cloud, as AI tools proliferate, and as threat actors adopt business-like models, the battlefield has shifted dramatically. According to analysts at Gartner, 2025 brings some of the most significant inflections in cybersecurity in recent memory. 

In this article we’ll cover the major trends, why they matter, and — importantly — what you as an individual or sysadmin can start doing today to stay ahead.

1. Generative AI: Weapon and Shield

AI / ML (Machine Learning)) is now deeply ingrained in both the offence and defence sides of cybersecurity.

  • On the defence side: AI models help detect anomalies, process huge volumes of logs, and automate responses. 
  • On the offence side: Attackers use AI to craft more convincing phishing campaigns, automate vulnerability discovery, generate fake identities or even design malware. 
  • Data types are changing: It’s no longer just databases and spreadsheets. Unstructured data (images, video, text) used by AI models is now a primary risk.

What to do:

  • Make sure any sensitive AI-training data or inference logs are stored securely.
  • Build anomaly-detection systems that don’t assume “normal” traffic anymore.
  • Flag when your organisation uses AI tools: do you know what data the tool uses, where it stores it, who can access it?

2. Zero Trust Isn’t Optional Anymore

 

cyber-security-threats-to-watch-in-2025

The old model — trust everything inside the perimeter, block everything outside — is obsolete. Distributed workforces, cloud services, edge devices: they all blur the perimeter. Hence the rise of Zero Trust Architecture (ZTA) — “never trust, always verify.” INE+1

Key features:

  • Every device, every user, every session must be authenticated and authorised.
  • Least-privilege access: users should have the minimum permissions needed.
  • Micro-segmentation: limit lateral movement in networks.
  • Real-time monitoring and visibility of sessions and devices.

What to do:

  • Audit your devices and users: who has broad permissions? Who accesses critical systems?
  • Implement multifactor authentication (MFA) everywhere you can.
  • Review network segmentation: can a compromised device access everything? If yes, that’s a red flag.
     

3. Ransomware & RaaS – The Business Model of Cybercrime

Cybercriminals are organizing like businesses: they have supply chains, service models, profit centres. The trend of Ransomware‑as‑a‑Service (RaaS) continues to expand. Dataconomy+1

What’s changed:

  • Ransomware doesn’t just encrypt data. Attackers often steal data first, then threaten to release it. 
  • Attackers are picking higher-value targets and critical infrastructure.
  • The attack surface has exploded: IoT devices, cloud mis-configurations, unmanaged identity & access.

What to do:

  • Back up your critical systems regularly — test restores, not just backups.
  • Keep systems patched (though even fully patched systems can be attacked, so patching is necessary but not sufficient).
  • Monitor for abnormal behaviour: large data exfiltration, new admin accounts, sudden access from odd places.
  • Implement strong incident response procedures: when it happens, how do you contain it?

4. Supply Chains, IoT & Machine Identities

Modern IT is no longer just endpoints and servers. We have IoT devices, embedded systems, cloud services, machine-to-machine identities. According to Gartner, machine identities are expanding attack surfaces if unmanaged.

Key issues:

  • Devices (especially IoT) often ship with weak/default credentials.
  • Machine identities: software services, APIs, automation tools need their own identity/access management.
  • Supply chains: your vendor might be the weakest link — compromise of software or hardware upstream affects you.

What to do:

  • Create an inventory of all devices and services — yes all.
  • Enforce device onboarding processes: credentials changed, firmware up-to-date, network segmented.
  • Review your vendors: what security standards do they follow? Do they give you visibility into their supply chain risk?
     

5. Cloud & Data Privacy — New Rules, New Risks

As data moves into the cloud and into AI systems, the regulatory and technical risks converge. For example, new laws like the EU AI Act will start affecting how organisations handle AI usage and data. Source: Gcore
Cloud environments also bring mis-configurations, improper access controls, shadow-IT and uncontrolled data sprawl. techresearchs.com+1

What to do:
 

  • If using cloud services, check settings for major risk zones (e.g., S3 buckets, unsecured APIs).
  • Implement strong Identity & Access Management (IAM) controls for cloud resources.
  • Make data-privacy part of your security plan: what data you collect, where it is stored, for how long.
  • Perform periodic audits and compliance checks especially if you handle users from different jurisdictions.
     

6. Skills, Culture & Burn-out — The Human Factor

Often overlooked: no matter how good your tech is, people and culture matter. Gartner Security behaviour programs help reduce human-error incidents — and they’re becoming more essential.
Also, the cybersecurity talent shortage and burnout among security teams is real.

What to do:

 

  • Invest in security awareness training: phishing simulation, strong password practices, device hygiene.
  • Foster a culture where security is everyone’s responsibility, not just the “IT team’s problem.”
  • For small teams: consider managed security services or cloud-based monitoring to lean on external support.

7. What This Means for Smaller Organisations & Individual Users

Often the big reports focus on enterprises. But smaller organisations (and individual users) are just as vulnerable — sometimes more so, because they have fewer resources and less mature security.
Here are some concrete actions:

  • Use strong, unique passwords and a password manager.
  • Enable MFA everywhere (email, online services, VPNs).
  • Keep your systems updated — OS, applications, firmware.
  • Be suspicious of unexpected communications (phishing).
  • Have an incident response plan: who do you call if things go wrong?
  • Backup your data offline and test restores.
  • If you run services (web-server, mail server): monitor logs, check for new accounts, stray network connections.
     

Conclusion

Cybersecurity in 2025 is not a “set once and forget” system. It’s dynamic, multi-layered and deeply integrated into business functions and personal habits. The trends above — generative AI, zero trust, supply chain risks, cloud data sprawl — are changing the rules of the game.
Thus for all of us and especially sysadmins / system engineers or Site Reliabiltiy Managers (SRE), Developers, Testers or whatever you call it this meen we need to keep learning, be careful with the tech stuff we use, and build security as a continuous practice rather than a one-off box-to-tick.

 

How to Restrict a Program to Use Only a Specific Port Range for Outgoing Connections (Linux)

Wednesday, October 22nd, 2025

In some advanced system administration and security scenarios, you might want to restrict a specific program (or a user) to only use a specific range of source ports when making outbound connections. This can be important for:

  • Securing network access per service
  • Enforcing port-based identity
  • Making firewall rules easier to manage
  • Avoiding source port collisions in NAT environments

This article explains how to achieve this using iptables, cgroups, and kernel-level ephemeral port controls, with practical, copy-paste-ready examples.

What meant by "Port Range Restriction"?

When a program connects to an external server (e.g., with curl or a custom app), the kernel assigns a source port — typically from a dynamic range like 32768–60999. If you want to limit a specific app or user to only use, say, source ports 50000–51000 for all its outbound connections, you have to implement that at the firewall or kernel level.

Option 1: Using iptables with the owner Module (Per-User Limiting)

This is the most straightforward way if the program runs as a known user or UID.

Example: Allow only source ports 50000–51000 for myuser

# Block all outgoing TCP traffic from 'myuser' outside the port range

# iptables -A OUTPUT -p tcp -m owner –uid-owner myuser ! –sport 50000:51000 -j DROP

 

# Optional: for UDP

# iptables -A OUTPUT -p udp -m owner –uid-owner myuser ! –sport 50000:51000 -j DROP

Note: This will not stop the application from running — it just ensures the kernel drops packets that don't meet your policy.

Option 2: Using cgroups with net_cls + iptables (Per-Process Control)

For more precise control, especially in multi-user systems or containers, you can use Linux cgroups to classify traffic per process.

Step-by-step:

  1. Create a net_cls cgroup:

# mkdir -p /sys/fs/cgroup/net_cls/myapp

# echo 0x100001 | sudo tee /sys/fs/cgroup/net_cls/myapp/net_cls.classid

  1. Assign your process to the cgroup:

# Replace <PID> with the actual process ID

# echo <PID> | sudo tee /sys/fs/cgroup/net_cls/myapp/tasks

  1. Add iptables rule:

# iptables -A OUTPUT -m cgroup –cgroup 0x100001 -p tcp ! –sport 50000:51000 -j DROP


This gives you fine-grained per-process port restrictions.


Optional: Set Ephemeral Port Range (Globally or Per Namespace)

Linux picks ephemeral ports from a default system-wide range (32768–60999). You can limit this globally (not recommended in shared systems) or within a network namespace.

To view the current range:

# cat /proc/sys/net/ipv4/ip_local_port_range

To change the range:

# echo "50000 51000" | sudo tee /proc/sys/net/ipv4/ip_local_port_range

Warning: This affects all processes. Better used inside isolated environments (e.g., containers or netns).

Run the App with Explicit bind() to Source Port

If you control the source code of the app, the cleanest method is to explicitly bind to the desired source port(s) before making the connection.

Python Example:

import socket

 

s = socket.socket()

s.bind(('', 50001))  # bind to specific source port

s.connect(('example.com', 443))

Many apps and libraries (like curl, wget, netcat, python requests, etc.) don't expose source port binding — in those cases, control must be done externally.

Logging Violations

To log connection attempts from disallowed source ports:
 

# iptables -A OUTPUT -p tcp -m owner –uid-owner myuser ! –sport 50000:51000 \

  -j LOG –log-prefix "PORT VIOLATION: " –log-level 4
 

Containerized or Isolated Environments: Use Namespaces or Docker

For Docker or custom network namespaces, you can:

  • Use a private ephemeral port range
  • Use iptables inside the namespace
  • Isolate the entire network stack for maximum safety

Docker Example:

 # docker run -d –name limited_app –net=custom_net -p 50000-51000:50000-51000 myimage

Then configure the app inside the container to bind or use that port range.

Important Limitations

  • iptables owner match works only for UIDs, not process names.
  • If the app doesn’t bind explicitly, the kernel still picks a port — make sure the global range or netns is set correctly.
  • Some apps (e.g. Java, browsers) may use multiple ephemeral ports simultaneously — test carefully.
  • Not all programs allow setting source port via command line.

Recommended Best Practices

Let’s say you want to allow only ports 50000–51000 for a service called myapp:

  1. Create a dedicated system user:

# sudo useradd -r -s /sbin/nologin myapp

  1. Run the program as this user:

# sudo -u myapp /path/to/myapp

  1. Set the desired port range system-wide (optional):

# echo "50000 51000" | sudo tee /proc/sys/net/ipv4/ip_local_port_range

  1. Add iptables rule:

# iptables -A OUTPUT -p tcp -m owner –uid-owner myapp ! –sport 50000:51000 -j DROP

  1. (Optional) Add logging:

# iptables -A OUTPUT -p tcp -m owner –uid-owner myapp ! –sport 50000:51000 -j LOG –log-prefix "MYAPP PORT BLOCKED: "

  1. Make sure the app binds or connects correctly from that range.

Final Thoughts

Restricting outbound source port ranges per application or user is an underused but powerful technique to lock down systems, especially in:

  • High-security environments
  • Multi-tenant servers
  • NAT/firewall-managed networks
  • IDS/IPS-aware setups


You can start with simple iptables rules for users, and later adopt cgroups or container namespaces for deeper isolation.

Force specific source ports for a app like curl

To force specific source ports in general-purpose tools like curl or nc, try using iptables SNAT (with care), or consider writing wrappers/scripts that handle binding via LD_PRELOAD or custom socket logic.

How to Build a Linux Port Scan Honeypot with iptables and test with nmap

Wednesday, October 8th, 2025

buiild-own-honeypot-with-iptables-and-test-with-nmap-linux-howto

Let’s build a simple, practical Linux port‑scan honeypot that uses iptables to capture/redirect incoming connection attempts and simple listeners that log interaction, and use nmap to test it. I’ll give runnable commands, a small Python honeypot listener, iptables rules (with rate‑limited logging), how to test with nmap, and notes on analysis, hardening, and safety.

Important safety notes (read first)

  • Only deploy this on machines/networks you own or are explicitly authorized to test. Scanning other people’s systems or letting an exposed honeypot be used to attack others is illegal and unethical.
  • Run the honeypot inside an isolated VM or protected network segment (use separate host or VLAN), and don't forward traffic from other networks unless you understand the risks.
  • Keep logs rotated and watch disk usage — honeypots can attract high traffic.

Overview

  1. Prepare an isolated Linux VM.
  2. Add iptables rules to log and optionally redirect connection attempts to local listeners.
  3. Run simple service emulators (Python) to accept connections and log data.
  4. Use nmap to test and validate behavior.
  5. Collect, parse, and analyze logs; optionally integrate with syslog/ELK.

Setup assumptions

  • Ubuntu/Debian or similar Linux.
  • Root (sudo) access.
  • iptables, python3, nmap, and socat/netcat available (I’ll show alternatives).

1) Create a safe environment

  • Use a VM (VirtualBox/QEMU/KVM) or container with no sensitive data.
  • Give it a fixed private IP on an isolated network.
  • Optionally place the VM behind another firewall (so you can control exposure).

2) Simple iptables logging rules (filter table)

We want to log connection attempts but avoid log flooding by using limit. Put these commands in a root shell:

# create a honeypot chain

# iptables -N HONEY

# rate-limited LOG then return so traffic continues to normal behavior

# iptables -A HONEY -m limit –limit 5/min –limit-burst 10 -j LOG \
–log-prefix "HPOT_CONN: " –log-level 4

# record raw packets to kernel log (optional NFLOG/ulogd is better for high volume)

# allow the packet to continue (or drop if you want it to appear closed)

# iptables -A HONEY -j ACCEPT

 

# Apply HONEY chain to incoming TCP attempts to all ports (or a subset)

# iptables -A INPUT -p tcp -j HONEY

Notes:

  • This logs packets hitting INPUT. If your VM is behind NAT or you want to catch forwarded packets, apply to FORWARD too.
  • Using -j LOG writes to kernel log, often /var/log/kern.log or via rsyslog to /var/log/messages.
  • Use NFLOG (-j NFLOG) with ulogd if you want structured logging and higher throughput.

If you prefer to log only SYNs (new connection attempts) to reduce noise:

# iptables -A HONEY -p tcp –syn -m limit –limit 5/min –limit-burst 10 \
-j LOG –log-prefix "HPOT_SYN: " –log-level 4

If you want to redirect certain destination ports into local port listeners (so local fake services can accept connections), use the nat table PREROUTING:

# Example redirect incoming port 80 to local port 8080

# iptables -t nat -A PREROUTING -p tcp –dport 80 -j REDIRECT –to-ports 8080

# For ports other than 80/443, add other rules

# iptables -t nat -A PREROUTING -p tcp –dport 22 -j REDIRECT –to-ports 2222

Notes:

  • -t nat -A PREROUTING catches traffic before routing. Use OUTPUT if traffic originates on host itself.
  • REDIRECT only works for local listeners.

3) Simple Python multi‑port honeypot listener

This script will bind multiple ports and log incoming connections and first bytes (banner or payload). Save as simple_honeypot.py:

#!/usr/bin/env python3

# simple_honeypot.py

import socket, threading, logging, time

 

LISTEN_PORTS = [22, 80, 443, 8080, 2222]   # customize

BANNER = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"

 

logging.basicConfig(

    filename="/var/log/honeypot.log",

    level=logging.INFO,

    format="%(asctime)s %(message)s"

)

 

def handle_client(conn, addr, port):

    try:

        conn.settimeout(5.0)

        data = conn.recv(4096)

        logged = data[:100].decode('utf-8', errors='replace')

        logging.info("CONNECT %s:%s -> port=%d first=%s", addr[0], addr[1], port, logged)

        # Send a small believable banner depending on port

        if port in (80, 8080):

            conn.sendall(BANNER)

        elif port in (22, 2222):

            conn.sendall(b"SSH-2.0-OpenSSH_7.4p1\r\n")

        else:

            conn.sendall(b"220 smtp.example.com ESMTP\r\n")

    except Exception as e:

        logging.info("ERROR %s", e)

    finally:

        try:

            conn.close()

        except:

            pass

 

def start_listener(port):

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    s.bind(('0.0.0.0', port))

    s.listen(50)

    logging.info("Listening on %d", port)

    while True:

        conn, addr = s.accept()

        t = threading.Thread(target=handle_client, args=(conn, addr, port), daemon=True)

        t.start()

 

if __name__ == "__main__":

    for p in LISTEN_PORTS:

        t = threading.Thread(target=start_listener, args=(p,), daemon=True)

        t.start()

    # keep alive

    while True:

        time.sleep(3600)

Run it as root (or with capability binding) so it can listen on low ports:
 

# python3 simple_honeypot.py


# check logs

# tail -F /var/log/honeypot.log

Alternatives:

  • Use socat to spawn a logger for individual ports.
  • Use cowrie, glastopf, honeyd, sshpot etc. if you want full featured honeypots.

4) Using nmap to test the honeypot

Only scan your honeypot IP. Example commands:

Basic TCP port scan:

# nmap -sS -p- -T4 -oN scan_ports.txt <HONEYPOT_IP>

Service/version detection:

# nmap -sV -sC -p 22,80,443,2222 -oN scan_svcs.txt <HONEYPOT_IP>

Scan while avoiding ping (if your VM blocks ICMP):

# nmap -Pn -sS -p1-2000 <HONEYPOT_IP>

Aggressive scan + scripts (use on your own host only):

# nmap -A -T4 <HONEYPOT_IP>

Watch your honeypot logs while scanning to confirm entries.

5) Log collection & analysis

  • Kernel log: sudo tail -F /var/log/kern.log or sudo journalctl -f
  • Python listener log: /var/log/honeypot.log
  • For structured logging and higher throughput: configure ulogd (NFLOG) or forward logs to syslog/rsyslog and ELK/Graylog.
  • Example simple grep: grep HPOT_CONN /var/log/kern.log | tail -n 200

6) Hardening & operational tips

  • Isolate honeypot inside a VM and snapshot it so you can revert.
  • Use rate limits in iptables to avoid log flooding: -m limit –limit 5/min.
  • Rotate logs: configure logrotate for /var/log/honeypot.log.
  • Do not allow outbound traffic from the honeypot unless needed – attackers may use it to pivot. Use egress firewall rules to restrict outbound connections.
  • Consider running the honeypot under an unprivileged user wherever possible.
  • If expecting a lot of traffic, use NFLOG + ulogd or suricata/zeek for more scalable capture.

7) Optional: Richer visibility with NFLOG / ulogd

If you anticipate higher volume or want structured logs, use:

# example: mark packets from all TCP and send to NFLOG group 1

# iptables -I INPUT -p tcp -m limit –limit 10/min -j NFLOG –nflog-group 1

# run ulogd to write NFLOG to a file or DB (configure /etc/ulogd.conf)

8) Example scenario — bring it all together

  1. Start VM and ensure IP 192.168.56.101.
  2. Add iptables logging and redirect HTTP->8080.
  3. Run simple_honeypot.py with ports [22,80,2222,8080].
  4. From your scanner machine: nmap -sS -sV -p22,80,2222,8080 192.168.56.101
  5. Watch /var/log/honeypot.log and kernel logs for HPOT_* prefixes to see connections and payloads.

Quick reference for commands used:

  • Create honeypot chain and log SYNs:

# iptables -N HONEY

# iptables -A HONEY -p tcp –syn -m limit –limit 5/min –limit-burst 10 \
-j LOG –log-prefix "HPOT_SYN: "

# iptables -A INPUT -p tcp -j HONEY

  • Redirect port 80 -> 8080

# iptables -t nat -A PREROUTING -p tcp –dport 80 -j REDIRECT –to-ports 8080

  • Run listener:

# python3 simple_honeypot.py

  • Test with nmap:

# nmap -sS -sV -p22,80,2222,8080 -Pn <IP>

Conclusion

Building a simple Linux port‑scan honeypot with iptables and lightweight listeners gives you practical, immediate visibility into who’s probing your network and how they probe. With a few well‑scoped iptables rules you can capture and rate‑limit connection attempts, redirect selected ports to local emulators, and keep tidy, analysable logs. The small Python listener shown is enough to collect banners and initial payloads; for higher volume or more fidelity you can step up to NFLOG / ulogd, Zeek / Suricata, or production honeypots like Cowrie.

Remember the two rules that keep a honeypot useful and safe: isolate it (VMs, VLANs, strict egress rules) and log thoughtfully (rate limits, rotation, structured formats). That protects your environment and makes the data you collect far more valuable. Over time, turn raw logs into indicators (IP reputations, patterns of ports/probes, common payloads) and feed them into alerts or dashboards to turn passive observation into active defense.

How to Create Secure Stateful Firewall Rules with nftables on Linux

Monday, October 6th, 2025

nftables-logo-linux-mastering-stateful-firewall-rules-with-nftables-firewall

Firewalls are the frontline defense of any server or network. While many sysadmins are familiar with iptables, nftables is the modern Linux firewall framework offering more power, flexibility, and performance.

One of the key features of nftables is stateful packet inspection, which lets you track the state of network connections and write precise rules that dynamically accept or reject packets based on connection status.

In this guide, we’ll deep dive into stateful firewall rules using nftables — what they are, why they matter, and how to master them for a robust, secure network.

What is Stateful Firewalling?

A stateful firewall keeps track of all active connections passing through it. It monitors the state of a connection (new, established, related, invalid) and makes decisions based on that state rather than just static IP or port rules.

This allows:

  • Legitimate traffic for existing connections to pass freely
  • Blocking unexpected or invalid packets
  • Better security and less manual rule writing

Understanding Connection States in nftables

nftables uses the conntrack subsystem to track connections. Common states are:

State

Description

new

Packet is trying to establish a new connection

established

Packet belongs to an existing connection

related

Packet related to an existing connection (e.g. FTP data)

invalid

Packet that does not belong to any connection or is malformed

Basic Stateful Rule Syntax in nftables

The key keyword is ct state. For example:

nft add rule inet filter input ct state established,related accept

This means: allow any incoming packets that are part of an established or related connection.

Step-by-Step: Writing a Stateful Firewall with nftables

  1. Create the base table and chains


nft add table inet filter

nft add chain inet filter input { type filter hook input priority 0 \; }

nft add chain inet filter forward { type filter hook forward priority 0 \; }

nft add chain inet filter output { type filter hook output priority 0 \; }

  1. Allow loopback traffic

nft add rule inet filter input iif lo accept

  1. Allow established and related connections

nft add rule inet filter input ct state established,related accept

  1. Drop invalid packets

nft add rule inet filter input ct state invalid drop

  1. Allow new SSH connections

nft add rule inet filter input tcp dport ssh ct state new accept

  1. Drop everything else

nft add rule inet filter input drop

Why Use Stateful Filtering?

  • Avoid writing long lists of rules for each connection direction
  • Automatically handle protocols with dynamic ports (e.g. FTP, SIP)
  • Efficient resource usage and faster lookups
  • Better security by rejecting invalid or unexpected packets
nftables-tcpip-model-diagram-logo

Advanced Tips for Stateful nftables Rules

  • Use ct helper for protocols requiring connection tracking helpers (e.g., FTP)
  • Combine ct state with interface or user match for granular control
  • Use counters with rules to monitor connection states
  • Rate-limit new connections using limit rate with ct state new

Real-World Example: Preventing SSH Brute Force with Stateful Rules

nft add rule inet filter input tcp dport ssh ct state new limit rate 5/minute accept

nft add rule inet filter input tcp dport ssh drop

This allows only 5 new SSH connections per minute.

Troubleshooting Stateful Rules

  • Use conntrack -L to list tracked connections
  • Logs can help; enable logging on dropped packets temporarily
  • Check if your firewall blocks ICMP (important for some connections)
  • Remember some protocols may require connection helpers

Making Your nftables Rules Permanent

By default, any rules you add using nft commands are temporary — they live in memory and are lost after a reboot.

To make your nftables rules persistent, you need to save them to a configuration file and ensure they're loaded at boot.

Option 1. Using the nftables Service (Preferred on Most Distros)

Most modern Linux distributions (Debian ≥10, Ubuntu ≥20.04, CentOS/RHEL ≥8) come with a systemd service called nftables.service that automatically loads rules from /etc/nftables.conf at boot.

 Do the following to make nftables load on boot:

Dump your current rules into a file:

# nft list ruleset > /etc/nftables.conf

Enable the nftables service to load them at boot:

# systemctl enable nftables

(Optional) Start the service immediately if it’s not running:

# systemctl start nftables

Check status:

# systemctl status nftables

Now your rules will survive reboots.

Alternative way to load nftables on network UP, Use Hooks in
/etc/network/if-pre-up.d/ or Custom Scripts (Advanced)

If your distro doesn't use nftables.service or you're on a minimal setup (e.g., Alpine, Slackware, older Debian), you can load the rules manually at boot:

Save your rules:

# nft list ruleset > /etc/nftables.rules

Create a script to load them (e.g., /etc/network/if-pre-up.d/nftables):

#!/bin/sh

nft -f /etc/nftables.rules

Make it executable:

chmod +x /etc/network/if-pre-up.d/nftables

This method works on systems without systemd.

Sample /etc/nftables.conf config

We first define variables which we can use later on in our ruleset:

 

define NIC_NAME = "eth0"

define NIC_MAC_GW = "DE:AD:BE:EF:01:01"

define NIC_IP = "192.168.1.12"

define LOCAL_INETW = { 192.168.0.0/16 }

define LOCAL_INETWv6 = { fe80::/10 }

define DNS_SERVERS = { 1.1.1.1, 8.8.8.8 }

define NTP_SERVERS = { time1.google.com, time2.google.com, time3.google.com, time4.google.com }

define DHCP_SERVER = "192.168.1.1"

Next code block shows ip filter and ip6 filter sample:

We first create an explicit deny rule (policy drop;) for the chain input and chain output.
This means all network traffic is dropped unless its explicitly allowed later on.

Next we have to define these exceptions based on network traffic we want to allow.
Loopback network traffic is only allowed from the loopback interface and within RFC loopback network space.

nftables automatically maps network protocol names to port numbers (e.g. HTTPS 443).
In this example, we only allow incoming sessions which we initiated (ct state established accept) from ephemeral ports (dport 32768-65535). Be aware an app or web server should allow newly initiated sessions (ct state new).

Certain network sessions initiated by this host (ct state new,established accept) in the chain output are explicitly allowed in the output chain. We also allow outgoing ping requests (icmp type echo-request), but do not want others to ping this host, hence ct state established in the icmp type input chain. 

table ip filter {

    chain input {

       type filter hook input priority 0; policy drop;

       iifname "lo" accept

       iifname "lo" ip saddr != 127.0.0.0/8 drop

       iifname $NIC_NAME ip saddr 0.0.0.0/0 ip daddr $NIC_IP tcp sport { ssh, http, https, http-alt } tcp dport 32768-65535 ct state established accept

       iifname $NIC_NAME ip saddr $NTP_SERVERS ip daddr $NIC_IP udp sport ntp udp dport 32768-65535 ct state established accept

       iifname $NIC_NAME ip saddr $DHCP_SERVER ip daddr $NIC_IP udp sport bootpc udp dport 32768-65535 ct state established log accept

       iifname $NIC_NAME ip saddr $DNS_SERVERS ip daddr $NIC_IP udp sport domain udp dport 32768-65535 ct state established accept

       iifname $NIC_NAME ip saddr $LOCAL_INETW ip daddr $NIC_IP icmp type echo-reply ct state established accept

    }

 

    chain output {

       type filter hook output priority 0; policy drop;

       oifname "lo" accept

       oifname "lo" ip daddr != 127.0.0.0/8 drop

       oifname $NIC_NAME ip daddr 0.0.0.0/0 ip saddr $NIC_IP tcp dport { ssh, http, https, http-alt } tcp sport 32768-65535 ct state new,established accept

       oifname $NIC_NAME ip daddr $NTP_SERVERS ip saddr $NIC_IP udp dport ntp udp sport 32768-65535 ct state new,established accept

       oifname $NIC_NAME ip daddr $DHCP_SERVER ip saddr $NIC_IP udp dport bootpc udp sport 32768-65535 ct state new,established log accept

       oifname $NIC_NAME ip daddr $DNS_SERVERS ip saddr $NIC_IP udp dport domain udp sport 32768-65535 ct state new,established accept

       oifname $NIC_NAME ip daddr $LOCAL_INETW ip saddr $NIC_IP icmp type echo-request ct state new,established accept

    }

 

    chain forward {

       type filter hook forward priority 0; policy drop;

    }

}

 

The next code block is used to block incoming and outgoing IPv6 traffic, except ping requests (icmpv6 type echo-request) and IPv6 network discovery (nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert).

vNICs are often automatically provisioned with IPv6 addresses and left untouched. These interfaces can be abused by malicious entities to tunnel out confidential data or even a shell.

table ip6 filter {

    chain input {

       type filter hook input priority 0; policy drop;

       iifname "lo" accept

       iifname "lo" ip6 saddr != ::1/128 drop

       iifname $NIC_NAME ip6 saddr $LOCAL_INETWv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } ct state established accept

    }

 

    chain output {

       type filter hook output priority 0; policy drop;

       oifname "lo" accept

       oifname "lo" ip6 daddr != ::1/128 drop

       oifname $NIC_NAME ip6 daddr $LOCAL_INETWv6 icmpv6 type echo-request ct state new,established accept

    }

 

    chain forward {

       type filter hook forward priority 0; policy drop;

    }

}

 Last code block is used for ARP traffic which limits ARP broadcast network frames:

table arp filter {

   chain input {

       type filter hook input priority 0; policy accept;

       iif $NIC_NAME limit rate 1/second burst 2 packets accept

   }

 

   chain output {

       type filter hook output priority 0; policy accept;

   }

}

To load up nftables rules

# systemctl restart nftables && systemctl status nftables && nft list ruleset

Test Before Save and Apply

NB !!! Always test your rules before saving them permanently. A typo can lock you out of your server !!!

Try:

# nft flush ruleset

# nft -f /etc/nftables.conf


!!! Make sure to test your ports are truly open or closed. You can use nc, telnet or tcpdump for this. !!!

Or use a screen or tmux session and set a watchdog timer (e.g., at now +2 minutes reboot) so you can recover if something goes wrong.

Conclusion

In the ever-evolving landscape of network security, relying on static firewall rules is no longer enough. Stateful filtering with nftables gives sysadmins the intelligence and flexibility needed to deal with real-world traffic — allowing good connections, rejecting bad ones, and keeping things efficient.

With just a few lines, you can build a firewall that’s not only more secure but also easier to manage and audit over time.Whether you're protecting a personal server, a VPS, or a corporate gateway, understanding ct state is a critical step in moving from "good enough" security to proactive, intelligent defense.
If you're still relying on outdated iptables chains with hundreds of line-by-line port filters, maybe it's time to embrace the modern way.
nftables isn’t just the future — it’s the present. Further on log, monitor, and learn from your own traffic.

Start with the basics, then layer on your custom rules and monitoring and enjoy your system services and newtork being a bit more secure than before.


Cheers ! 🙂

What is oddjobd and How to Use It Instead of sudo to run limited privileged execution of scripts requiring admin

Tuesday, September 30th, 2025

oddjobd-sudoers-linux-elevate-script-running-linux

In Linux environments, managing privileged operations for unprivileged users is a critical task. Traditionally, tools like sudo have been used to allow users to execute specific commands with elevated privileges. However, in more secure or fine-tuned environments — such as enterprise networks or identity-managed systems — oddjobd offers a more controlled, D-Bus-driven alternative.

This article explains what oddjobd is, how it works, and when you might prefer it over sudo, complete with real-world examples.

What is oddjobd?

oddjobd is a system service (daemon) that runs in the background and allows limited, controlled execution of privileged tasks on behalf of unprivileged users.

Key Features:

  • Allows secure execution of predefined scripts or programs as root (or another user).
  • Communicates over D-Bus for fine-grained access control.
  • Uses Polkit (PolicyKit) to manage who can run which tasks.
  • Commonly used in FreeIPA, SSSD, and LDAP-based environments.
  • Configuration files live in: /etc/oddjobd.conf.d/

How It Works

  • System administrators define specific jobs (scripts or commands) in config files.
  • These jobs are exposed via D-Bus.
  • Unprivileged users (or applications) can request jobs to be executed.
  • Access is granted or denied by Polkit rules, not passwords.
  • No full shell or terminal access is granted — just the job.
 

oddjobd vs sudo

Feature

sudo

oddjobd

Control granularity

Medium (commands)

High (methods, scripts only)

Interactive shell

Yes

No

Config complexity

Simple (/etc/sudoers)

Moderate (conf.d + Polkit)

Uses system user password

Yes

Optional (can be passwordless via Polkit)

Security

Medium

High (no shell, strict policy control)

D-Bus compatible

No

Yes

Ideal for

Power users

Controlled environments (e.g., FreeIPA)

Typical Use Cases for oddjobd


1. Automatically Creating Home Directories


Problem: LDAP/FreeIPA users don’t have home directories created on login.

Solution: Enable oddjobd to create them via oddjob-mkhomedir.

# authconfig –enablemkhomedir –update

On login, PAM calls oddjobd, which creates the home directory as root.
 

2.  Restarting a Service without sudo

Let's say you want a user to restart Apache, but not give them full sudo rights.

a. Create a script

# /usr/local/bin/restart_apache.sh

#!/bin/bash

systemctl restart apache2

echo "Apache restarted by oddjob at $(date)"

chmod +x /usr/local/bin/restart_apache.sh

b. Create Oddjob config
 

# /etc/oddjobd.conf.d/restart_apache.conf

[restart_apache]

program = /usr/local/bin/restart_apache.sh

user = root

c. Polkit rule

 

// /etc/polkit-1/rules.d/60-restart-apache.rules

polkit.addRule(function(action, subject) {

    if (action.id == "org.freedesktop.oddjob.restart_apache" &&

        subject.isInGroup("apacheadmins")) {

        return polkit.Result.YES;

    }

});

 

d. Add user to group

# groupadd apacheadmins

# usermod -aG apacheadmins alice


e. Restart and test

# systemctl restart oddjobd


# As user "alice":

oddjob_request restart_apache


Only the defined method runs — no sudo shell access, no arbitrary commands.
 

3. GUI-friendly Device Control


Use Case: A user wants to reset a USB device via a button in a GUI app.

  • Define the method in oddjobd.
  • Use Polkit for GUI D-Bus permission.
  • The app can call the method securely, without sudo.

Advantages of oddjobd

More Secure Than sudo:

  • No interactive shell or terminal.
  • No command-line injection risks.
  • Can’t “escape” to a shell like with sudo bash.

Granular Control:

  • Limit tasks to a specific script or even script arguments.

D-Bus and GUI Friendly:

  • Apps can call privileged methods without shell hacks.

Policy-Based Authorization (Polkit):

  • Fine-grained user/group access control.
  • No password prompts if not desired.

Enterprise-Ready:

  • Works well with LDAP, FreeIPA, and centralized login environments.

Oddjobd Limitations / Downsides

Limitation

Description

Learning Curve

More complex to set up than sudo

Configuration Overhead

Requires writing config files and Polkit rules

Debugging

Issues may be harder to trace than sudo logs

Not for Ad-hoc Commands

Only predefined jobs can be run

Not Installed by Default

Often needs to be manually installed (oddjob, oddjob-mkhomedir)

When to Use oddjobd Instead of sudo

Use oddjobd when you:

  • Need to allow users or apps to run very specific privileged operations.
  • Want to avoid giving full shell access via sudo.
  • Are working in a managed enterprise environment.
  • Need GUI or D-Bus-based privilege escalation.
  • Require scripted access to root tasks without exposing credentials.

Conclusion

oddjobd is a powerful tool for securely handling privileged operations in Linux, especially where tight access control and automation are required. While sudo is simple and flexible, oddjobd shines in structured, security-conscious environments — particularly those using FreeIPA, LDAP, or automated tools.

If you need a more scriptable, policy-driven, and safer alternative to sudo for specific tasks, oddjobd is well worth exploring.

How to filter an IP, and IP range or domain to access to access service with /etc/hosts.allow /etc/hosts.deny , filtering Network range to sshd tcp port 22 through sshd service

Tuesday, June 4th, 2024

how-to-allow-and-deny-services-without-firewall-on-linux-logo-picture-tux

If you want to filter a range of IPs to be able to or unable to access a TCP port service because someone is trying to brute force you from the network or just because you don't want a connected LAN IPs to have access to your server for whatever security reasons. The simplest way you can do IP and IP range restrictions to allow or disable access towards a Linux server via defining allow or prohibition rules in  /etc/hosts.allow and /etc/hosts.deny.

This files are there and useful since the beginning of UNIX OS-es and has been widely used on Linux in the past and rarely known by people nowadays.

 

The hosts.allow and hosts.deny files could be used on a Linux system to deny connection attempts from one or more IP addresses, hostnames, or domains. 
/etc/hosts.allow and /etc/hosts.deny are just a plain text configuration file with a rather simple syntax, that can be used for decades to allow or filter IPs without applying a special firewall rules like iptables locally.
It can work with any TCP wrapped service on your system. The hosts.deny file is used in conjunction with hosts.allow to determine whether a connection attempt gets accepted or denied.

In this small tutorial, you will see an example of the hosts.allow file and how to use it to allow or deny connections to IPs or networks, as well as how a simple prohibition to access SSH service only via specific IP network can be done.

For full understanding of hosts.allow / hosts.deny file, check the manuals man hosts.allow , man hosts.deny, man hosts_options, man hosts_options.

root@pcfreak:~# apropos hosts|grep -iE '^hosts.*'
hosts.equiv (5)      – list of hosts and users that are granted "trusted" r command access to your system
hosts (5)            – static table lookup for hostnames
hosts.allow (5)      – format of host access control files
hosts.deny (5)       – format of host access control files
hosts_access (5)     – format of host access control files
hosts_options (5)    – host access control language extensions

General hosts.allow / hosts.deny syntax

The /etc/hosts.allow and /etc/hosts.deny understood syntax form is: 

service : host/network

Each value is separated by a colon :

You can also supply an option, but this is not as common. We will cover some other niche choices below. More options can be added if necessary, with each one separated by another colon.

service : host/network [:

The following line would allow all traffic to the sshd service. ALL is used as a wildcard.

sshd : ALL

Few examples to allow access to SSH Daemon from IPv4 and IPv6
This line would allow connections from all hosts on the 10.11 network. Connections from all other hosts can then be denied by the hosts.deny file. This type of configuration would work as intended since the allow line precedes our corresponding deny line in the other file, thus will be triggered first.

sshd : 10.11


Accept connections from a particular IPv4 and IPv6 address
 

sshd : 10.10.136.241
sshd : [2a02:2143:88f1:5c00:9991:9daa:b580:aee2]

 

Rather than using IPs, you can also specify hostnames to accept or deny connections from.

sshd : some.host

 

Accept connections from all hosts using the main domain .pc-freak.net domain name.

sshd : .pc-freak.net

You can also use a wildcard for both the service and the host/network field. This will accept all connections to any service. This would make all other rules (including those in hosts.deny) irrelevant, as all connections will be accepted by this rule before they have a chance to be denied.

ALL : ALL

The EXCEPT operator can be used to create an exception in an otherwise all allowing rule. 
For example, this rule would allow all connections from the .pc-freak.net domain name, except for one sub-domain org.pc-freak.net

sshd : .pc-freak.net EXCEPT org.pc-freak.net


Allow connectivity towards SSH TCP port 22 for all IP / hosts except for certain IPs and domains
 

To control connectivity towards sshd service via allow hosts  /etc/hosts.allow for all except a bad.host and a certain IP range:

 

sshd : ALL : allow
sshd : bad.host : deny
sshd : 85.5.1. : deny (1)

 

Disable access to all remote services to the network

Lets say if you're running the Linux as  desktop station and you want to disable access to any local services running on TCP ports

If you want to be paranoid and disable all remote access to server to any IP network, you can do it with:

# echo "ALL: ALL" >/etc/hosts.deny


Completely allow access to a certain running TCP port service on server
 

To allow completely access to a service
 

service_name : ALL : allow

Allow access for a a range of IPs subnet

You can also specifcy the IP netmask range to allow, like this:

ALL : 192.168.0.0/255.255.254.0

 

Allow access to all server network services for a domain except for a certain domain
 

Enable access to ALL running server services listening on TCP port except for domain

ALL : .example.com EXCEPT skiddie-attacker.example-domain.com


Allow access to al services except to a service for a local port range via hosts.allow

Here is example onw how to use hosts.allow file to allow connections all running server services except access to VSFTP, coming from Local LAN IPs with netmask /24 (e.g. from the 192.168.0.x.):

ALL EXCEPT vsftpd : 192.168.0

 


Filtering IPs and IP Ranges from within /usr/sbin/sshd openssh service via /etc/ssh/sshd_config (allow and disable access to concrete IPs trying to brute force you)
 


Lets say however, you don't want to do the filtering of openssh connections via hosts.allow / hosts.deny but rather on a SSH Service level, this can be done with the following /etc/ssh/sshd_config configuration.

# vim /etc/ssh/sshd_config

Match Address *,!192.168.1.0/24
    ForceCommand /bin/false

For more on the use of Match Address check documentation with man 5 sshd_config


To re-load the opensshd config

# systemctl restart sshd

 

Of course manually filtering villains is a tedious task and ultimately to save yourself time and inconvenience to regullary look up within /var/log/security or /var/log/messages (depending on the Linux distribution) and the configuration for SSHD to login imposters you would prefer to use fail2ban (if you're not familiar with fail2ban check out my previous article on how to easily Stop ssh bruteforce authentication attempt Attacks with fail2ban or if you want to use the Linux native way check out the article how to prevent SSH and FTP bruteforce attacks with IPtables.