Kalos Cybersecurity LLC

Appendix F — Version Pinning and Update Control

A security monitoring platform that changes underneath you is not a laboratory. It is a moving target. This guide shows how to pin Wazuh and Suricata to known versions on the course lab, how to prove the pin is actually working, and how to release it cleanly when the time comes.

The procedure is short. The verification is not, and that is deliberate. Most of what follows exists to answer a single question that separates an analyst from an operator: how do you know?

F.1 Why pin versions at all

Throughout this course you will capture screenshots, record rule IDs, and compare command output against printed expected results. Every one of those artifacts is tied to a specific software version. If the Wazuh manager upgrades itself midway through Lab 5, the dashboard chrome changes, field names may shift, and the output you are told to expect stops matching the output you see. You will spend an hour debugging a lab that was never broken.

Version pinning is not about avoiding updates forever. Wazuh 4.14.7 may well be better than 4.14.5. It is about controlling when change happens, so that change is an event you plan for rather than an accident you discover.

Lab version authority. Wazuh Manager, Indexer, and Dashboard: 4.14.5-1 · Suricata: 1:8.0.6-0ubuntu3 · Ubuntu: 26.04 · Amazon Linux: AL2023. These are the versions the labs were validated against. Do not change them mid-course.

Note. This appendix is referenced from three points in the course. Apply it once during Lab 0 setup, confirm it before each lab run, and reverse it only when a version upgrade is planned work. Appendix A.12 covers the Suricata service override on UB2604 and assumes the hold described here is already in place.

F.2 The two-layer model

Package managers can be stopped at two different points, and the distinction matters because the two layers fail in different ways.

A visibility lock hides the source. The package manager never learns that a newer version exists, so it cannot offer one. On Amazon Linux this is enabled=0 in the repository file; on Ubuntu it is a commented-out or disabled source entry.

An action lock permits the package manager to see the newer version but forbids it from acting. On Amazon Linux this is an exclude= directive; on Ubuntu it is apt-mark hold.

Use both. A visibility lock alone is fragile because ordinary events re-enable repositories: a reinstall that rewrites the repository file, a copied-and-pasted command from vendor documentation, a colleague running dnf config-manager. An action lock alone is fragile in a different way, because the package manager keeps advertising the upgrade and eventually somebody accepts it. Together they cover each other.

Layer Amazon Linux (dnf) Ubuntu (apt) Fails when
Visibility enabled=0 in the .repo file Source disabled or commented out Repository is re-enabled
Action exclude= under [main] apt-mark hold Someone deliberately removes it

F.3 Before you touch anything: back up and verify

Every step in this guide edits a configuration file in place. Make a copy first, and then prove the copy is good. A copy you have not verified is not a backup; cp can produce a zero-byte file if the filesystem is full, and you will not find out until you need to restore.

WAZUH-SRV · Terminal

sudo cp -p /etc/yum.repos.d/wazuh.repo /etc/yum.repos.d/wazuh.repo.bak-$(date +%F)
sudo cp -p /etc/dnf/dnf.conf /etc/dnf/dnf.conf.bak-$(date +%F)
sha256sum /etc/yum.repos.d/wazuh.repo /etc/yum.repos.d/wazuh.repo.bak-$(date +%F)
sha256sum /etc/dnf/dnf.conf /etc/dnf/dnf.conf.bak-$(date +%F)

Each pair of hashes must match, and no backup may be zero bytes. The -p flag preserves permissions, ownership, and timestamps, which matters when you restore. If the hashes do not match, stop and find out why before editing anything.

Note. Naming matters in /etc/yum.repos.d/. The dnf tool reads only files ending in .repo, so wazuh.repo.bak-2026-08-23 is safely ignored. A backup named wazuh-old.repo would be parsed as a second, fully enabled repository and would silently undo the freeze you are about to apply.

F.4 Freezing WAZUH-SRV (Amazon Linux 2023, dnf)

F.4.1 Inventory what is installed

Establish the starting state before you change it. You cannot demonstrate that a freeze worked without a recorded baseline.

WAZUH-SRV · Terminal

rpm -qa | grep -Ei 'wazuh|filebeat' | sort
grep -n 'enabled' /etc/yum.repos.d/wazuh.repo

Expect three packages at 4.14.5-1 and enabled=1 on line 4. Wazuh 4.8 and later replaced Filebeat with the manager built-in indexer connector, so on a current all-in-one build no Filebeat package appears. If yours does, pin it too.

F.4.2 Layer one: disable the repository

Preview the change before committing it. Without the -i flag, sed prints the modified file and writes nothing, which makes it a safe dry run.

WAZUH-SRV · Terminal

sed 's/^enabled=1/enabled=0/' /etc/yum.repos.d/wazuh.repo | diff /etc/yum.repos.d/wazuh.repo -

The expected output is a single changed line. If diff reports more than one, the file is not what you expect and the edit would cause collateral damage.

WAZUH-SRV · Expected output

4c4
< enabled=1
---
> enabled=0

Now apply it. The only difference is -i, and that difference is the whole point.

WAZUH-SRV · Terminal

sudo sed -i 's/^enabled=1/enabled=0/' /etc/yum.repos.d/wazuh.repo
grep -n 'enabled' /etc/yum.repos.d/wazuh.repo

The single most common mistake. Running sed without -i prints the modified file to the screen. The output looks exactly like a successful edit. Nothing is saved. Always confirm with a separate command that reads the file from disk, such as grep or cat -n. Never accept the tool own output as proof that it wrote anything.

F.4.3 Layer two: add a global exclude

The exclude directive must sit inside the [main] section. Placed anywhere else it silently does nothing, and dnf will not warn you. Inspect the file with line numbers first.

WAZUH-SRV · Terminal

cat -n /etc/dnf/dnf.conf

On a stock AL2023 build, [main] is on line 3 beneath a comment and a blank line, and it is the only section in the file. Preview the insertion, then apply it.

WAZUH-SRV · Terminal

sed '/^\[main\]/a exclude=wazuh-*' /etc/dnf/dnf.conf | diff /etc/dnf/dnf.conf -

WAZUH-SRV · Terminal

sudo sed -i '/^\[main\]/a exclude=wazuh-*' /etc/dnf/dnf.conf
cat -n /etc/dnf/dnf.conf

The result should place exclude=wazuh-* on line 4, directly under [main], with the original settings shifted down by one.

Note. Two settings already present in dnf.conf affect this work. best=False lets dnf accept an older version rather than fail, which quietly helps you. skip_if_unavailable=True causes an unreachable repository to be skipped rather than raising an error, which will hide network failures from you. Section F.6 shows how that setting can produce a false pass.

F.4.4 Verify the freeze

Refresh the metadata cache first, or you may be testing against stale data downloaded before the change.

WAZUH-SRV · Terminal

sudo dnf clean expire-cache
sudo dnf check-update 'wazuh*' 2>&1 | grep -Ei '^wazuh|Error|skip'; echo "exit=${PIPESTATUS[0]}"

Expect exit=0 with no package rows. An exit code of 100 means updates are still visible and the freeze did not take. The grep filter matters because Amazon Linux prints a long release-version banner that will otherwise bury the answer.

Then force the repository back on for a single command. This simulates the exact scenario the visibility layer cannot survive, and tests whether the action layer holds on its own.

WAZUH-SRV · Terminal

sudo dnf --enablerepo=wazuh check-update 'wazuh*' 2>&1 | grep -Ei '^wazuh|Error|skip'; echo "exit=${PIPESTATUS[0]}"

Still exit=0 with no rows. The –enablerepo flag applies to one command only and changes nothing on disk.

F.4.5 The positive control

Everything so far has proven a negative: nothing appeared. But nothing appears for two very different reasons. Either your freeze is working, or there is simply no newer version published. Those look identical and mean opposite things.

A positive control resolves this. Run the same query with the exclude bypassed for one command. If a newer version appears now but not before, you have changed exactly one variable and observed the difference.

WAZUH-SRV · Terminal

sudo dnf --enablerepo=wazuh --disableexcludes=all check-update 'wazuh*' 2>&1 | grep -Ei '^wazuh|Error|skip'; echo "exit=${PIPESTATUS[0]}"

Here you want the opposite result, and seeing it is the goal:

WAZUH-SRV · Expected output

wazuh-dashboard.x86_64    4.14.7-1    wazuh
wazuh-indexer.x86_64      4.14.7-1    wazuh
wazuh-manager.x86_64      4.14.7-1    wazuh
exit=100

The principle worth carrying beyond this lab. A test that cannot fail proves nothing. Before you trust a control, demonstrate that it detects the thing it is meant to detect. This is the same reasoning behind testing a detection rule with a benign trigger such as the EICAR file rather than assuming the rule works because no alerts fired.

F.5 Freezing UB2604 (Ubuntu 26.04, apt)

F.5.1 Apply the action lock

UB2604 · Terminal

sudo apt-mark showhold
sudo apt-mark hold wazuh-agent
sudo apt-mark showhold

The before-and-after pairing is the evidence. Suricata should already appear; wazuh-agent joins it.

F.5.2 Determine whether a repository even exists

Do not assume the Wazuh agent was installed from a repository. On the validated course build it was not.

UB2604 · Terminal

ls -l /etc/apt/sources.list.d/
grep -ril wazuh /etc/apt/ 2>/dev/null; echo "matches=$?"
apt-cache policy wazuh-agent

A grep result of matches=1 means no Wazuh repository is configured anywhere. The apt-cache policy output confirms it:

UB2604 · Expected output

wazuh-agent:
  Installed: 4.14.5-1
  Candidate: 4.14.5-1
  Version table:
 *** 4.14.5-1 100
        100 /var/lib/dpkg/status

The only listed source is /var/lib/dpkg/status, which is the dpkg record of what is installed rather than a repository. Apt has nowhere to fetch a newer agent from, so the package cannot drift. The hold is redundant today and worth keeping anyway: if anyone later adds the Wazuh repository, the lock is already in place.

F.5.3 Read apt-cache policy properly

This single command is the clearest freeze evidence apt offers, because it prints installed and candidate side by side rather than making you infer a result from silence.

UB2604 · Terminal

apt-cache policy suricata

UB2604 · Expected output

suricata:
  Installed: 1:8.0.6-0ubuntu3
  Candidate: 1:8.0.6-0ubuntu3
  Version table:
 *** 1:8.0.6-0ubuntu3 500
        500 https://ppa.launchpadcontent.net/oisf/suricata-stable/ubuntu resolute/main amd64 Packages
        100 /var/lib/dpkg/status
     1:8.0.3-1 500
        500 http://archive.ubuntu.com/ubuntu resolute/universe amd64 Packages

Read this carefully. Two live sources are configured, and the OISF PPA at priority 500 outranks the Ubuntu universe copy, which is why 8.0.6 is installed rather than 8.0.3. Candidate equals installed, so nothing newer is currently on offer.

That means the Suricata hold is armed but untested. It is not refusing anything today; there is simply nothing newer published yet. It will do real work the moment OISF ships 8.0.7. This is exactly the ambiguity the positive control in Section F.4.5 was designed to expose.

Note. Note the epoch. The true version string is 1:8.0.6-0ubuntu3, not 8.0.6. Apt treats those as different values. When you record a pinned version anywhere that a tool will read, use the full string including the epoch and the Ubuntu revision.

F.5.4 If a Wazuh repository is present

On a build that does use the Wazuh apt repository, add the visibility layer as well. Modern Ubuntu may store this in either the older one-line format or the newer deb822 format, and the edit differs.

UB2604 · Terminal

# one-line format: /etc/apt/sources.list.d/wazuh.list
sudo cp -p /etc/apt/sources.list.d/wazuh.list /etc/apt/sources.list.d/wazuh.list.bak-$(date +%F)
sudo sed -i 's/^deb /#deb /' /etc/apt/sources.list.d/wazuh.list
sudo apt update

UB2604 · Terminal

# deb822 format: /etc/apt/sources.list.d/wazuh.sources
sudo cp -p /etc/apt/sources.list.d/wazuh.sources /etc/apt/sources.list.d/wazuh.sources.bak-$(date +%F)
# add this line inside the stanza:
#   Enabled: no
sudo apt update

Commenting a deb line in a deb822 file matches nothing and silently accomplishes nothing. Check the file extension before choosing the command.

F.6 The false pass: when a clean result means nothing

During validation of this procedure the lab lost DNS resolution. The verification command produced this:

UB2604 · Expected output

Error encountered while trying to retrieve release update information:
Unable to retrieve release info data. Curl error (6): Could not resolve
hostname for https://cdn.amazonlinux.com/al2023/core/releasemd.xml
exit=0

Exit code zero. No packages listed. By the letter of the test, a pass. In reality dnf had reached nothing at all, and skip_if_unavailable=True suppressed the failure into a warning. A frozen host and a host with no network connection produce identical output here.

Always confirm name resolution and reachability before trusting a package-manager result.

WAZUH-SRV · Terminal

getent hosts packages.wazuh.com
curl -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' https://packages.wazuh.com/

A 403 response from that URL is expected and healthy: the content delivery network refuses a directory listing at the bucket root. What matters is that an HTTP status and a remote address came back at all. If getent returns only IPv6 addresses, check the remote_ip field to see which protocol the connection actually used before concluding that IPv6 is required.

F.7 Windows agents

WIN11 needs no freeze. The Windows agent has no self-update mechanism; it changes only when someone runs the installer or when the manager pushes an upgrade package. Both are deliberate acts, so the control here is procedural rather than technical: do not run agent_upgrade, and do not issue a PUT to the API upgrade endpoint, until the version bump is a planned piece of work with its own verification.

F.8 Unfreezing

Reverse the layers in the opposite order to the one you applied them. Take the action lock off last, so that the moment you make upgrades possible is a moment you have chosen.

WAZUH-SRV · Terminal

sudo sed -i '/^exclude=wazuh-\*/d' /etc/dnf/dnf.conf
sudo sed -i 's/^enabled=0/enabled=1/' /etc/yum.repos.d/wazuh.repo
cat -n /etc/dnf/dnf.conf
grep -n 'enabled' /etc/yum.repos.d/wazuh.repo

WAZUH-SRV · Terminal

sudo dnf clean expire-cache
sudo dnf repolist | grep -i wazuh
sudo dnf check-update 'wazuh*' 2>&1 | grep -Ei '^wazuh|Error|skip'; echo "exit=${PIPESTATUS[0]}"

Verify the unfreeze, not just the freeze. Because skip_if_unavailable=True hides unreachable repositories, an unfreeze that silently failed looks exactly like an unfreeze that worked. Confirm that Wazuh actually appears in dnf repolist output. Expect exit=100 and the newer packages listed: here, exit code 100 is the success condition.

UB2604 · Terminal

sudo apt-mark unhold wazuh-agent
sudo apt-mark showhold
apt-cache policy wazuh-agent

If you disabled an apt source, restore it from the verified backup rather than editing it back by hand.

UB2604 · Terminal

sudo cp -p /etc/apt/sources.list.d/wazuh.list.bak-YYYY-MM-DD /etc/apt/sources.list.d/wazuh.list
sudo apt update
apt-cache policy wazuh-agent

Note. An upgrade is more than a package change. Dashboard chrome, agent user interface elements, and version strings printed in command output all shift, which means every screenshot showing version information must be recaptured and every pinned version string in your notes updated. Plan the upgrade as a project, not as a command.

F.9 Troubleshooting: errors seen during validation

Every error below occurred while this procedure was being validated on the live lab. They are ordinary and worth recognising on sight.

Symptom Cause Fix
sed prints the whole file, but nothing changes on disk The -i flag was omitted, so sed ran in preview mode Re-run with -i, then confirm with cat -n
Shell shows a > prompt and waits An unterminated quote; the rest of the command was swallowed into the string Press Ctrl+C and retype with matching quotes
diff: missing operand after … The trailing - was omitted Append - so diff reads the piped stream as its second file
sha256: command not found Wrong tool name on Linux Use sha256sum
cp: target … is not a directory Three arguments given to cp, usually the source typed twice Re-run with exactly one source and one destination
Backup file named …-F rather than a date $(date +F) is missing the percent sign Use $(date +%F)
exit=0 with a Could not resolve host error above it Repository unreachable and skipped because of skip_if_unavailable=True Fix DNS, then re-test; the result was meaningless
exit=100 when you expected a clean freeze Updates are still visible; a layer did not apply Re-check both enabled=0 and the exclude line placement

F.10 Checkpoints

Record each result. A checkpoint you did not write down is a checkpoint you did not perform.

# Check Expected
1 sha256sum of each config and its backup Hashes match, no zero-byte files
2 grep -n enabled /etc/yum.repos.d/wazuh.repo 4:enabled=0
3 cat -n /etc/dnf/dnf.conf exclude=wazuh-* on line 4, under [main]
4 dnf check-update wazuh* exit=0, no package rows
5 dnf –enablerepo=wazuh check-update wazuh* exit=0, no package rows
6 dnf –enablerepo=wazuh –disableexcludes=all check-update exit=100, newer packages listed
7 rpm -qa grep wazuh
8 apt-mark showhold on UB2604 suricata and wazuh-agent both listed
9 apt-cache policy suricata Installed 1:8.0.6-0ubuntu3
10 getent hosts and curl before any dnf test Resolution and an HTTP status returned

F.11 Quick reference

Task Amazon Linux (dnf) Ubuntu (apt)
Hide the source enabled=0 in the .repo file Comment the deb line, or Enabled: no
Block the action exclude=wazuh-* under [main] sudo apt-mark hold PACKAGE
List blocks grep exclude /etc/dnf/dnf.conf apt-mark showhold
Check for updates dnf check-update PATTERN apt-cache policy PACKAGE
Bypass block once –disableexcludes=all No direct equivalent; unhold and re-hold
Enable repo once –enablerepo=NAME No direct equivalent
Refresh metadata dnf clean expire-cache sudo apt update
Success when frozen exit=0, no rows Candidate equals Installed
Success when unfrozen exit=100, rows listed Candidate newer than Installed

Two habits are worth taking from this guide into everything else you do. Always read the file back from disk rather than trusting a tool report of its own success. And before you rely on any control, prove that it is capable of failing.