Deployment guide

AlmaLinux 9 STIG

Overview

AlmaLinux is a community-driven enterprise Linux distribution, built by the AlmaLinux OS Foundation to be binary compatible with Red Hat Enterprise Linux 9. This SecureAnyCloud image is a preconfigured AlmaLinux 9 virtual machine for Microsoft Azure with the DISA STIG (Security Technical Implementation Guide) profile applied.

The profile comes from the SCAP Security Guide project and is applied at build time with oscap xccdf eval --remediate, the same open-source engine you can run yourself. The machine is then rebooted and re-scanned cleanly, with no remediation flag, to produce a measured compliance score. That score, the full HTML report and the machine-readable results all ship on the image.

This image is a compliance scanner and a pre-hardened image, not a compliance certificate. A passing score is a measurement made with the ComplianceAsCode community implementation of the DISA STIG benchmark. It is not a certification issued by DISA or any accreditation body. Read Step 4 before you rely on the number for an audit.

What you get:

  • AlmaLinux 9, binary compatible with RHEL 9, with the DISA STIG profile applied using AlmaLinux's own product-native SCAP Security Guide content (ssg-almalinux9-ds.xml), not a RHEL 9 stand-in
  • 571 installed packages on a fresh build, including the SCAP Security Guide content, aide, audit and the crypto-policy tooling the STIG profile pulls in
  • The build-time compliance report, results and measured score shipped at /var/log/sac/stig/
  • A second, independent re-verification that runs on your own VM shortly after first boot, and weekly after that
  • Every available update applied at build time, verified with a zero-pending check, and unattended updates already armed
  • SELinux in enforcing mode, both at runtime and persisted for the next boot
  • Passwordless sudo for your SSH-key-authenticated account, a deliberate and documented exception (see Step 3)
  • Azure Linux Agent (waagent) and cloud-init for Azure integration, proven to still work under the hardening
  • Gen2 Hyper-V virtual machine support
  • 24/7 SecureAnyCloud support

Platform: Microsoft Azure (Gen2 Hyper-V). Default user: azureuser.

1

Deploy the Virtual Machine

Option A: Azure Portal

  • Open the Azure Marketplace and search for "AlmaLinux 9 STIG-Hardened SecureAnyCloud".
  • Select the image and click Create.
  • On the basics page, set Subscription to your Azure subscription and Resource Group to a new or existing group.
  • Enter a name under Virtual Machine Name and choose your preferred Region.
  • Set Size to Standard_B2s (recommended).
  • Under Administrator Account, select SSH public key and enter your key.
  • Under Inbound Port Rules, allow SSH (port 22).
  • Click Review + Create, then Create.

Option B: Azure CLI

az vm image terms accept --urn sac1647283583153:almalinux-9-stig:default:latest
az vm create \
  --resource-group myResourceGroup \
  --name my-almalinux-9-stig-vm \
  --image sac1647283583153:almalinux-9-stig:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
2

Connect via SSH

Run these commands on your own workstation, not on the VM. Find the public IP, then connect:

az vm show --resource-group myResourceGroup --name my-almalinux-9-stig-vm --show-details --query publicIps -o tsv ssh azureuser@<vm-ip>

There is no password to enter. If SSH asks you for one, the key you supplied at create time is not the key your client is offering.

The STIG's sshd hardening tightens the ciphers, MACs and session timeouts shown below. The SSH connection model is the same as every other SecureAnyCloud image: key only, injected by Azure at first boot.

3

Sudo stays passwordless: read this first

The raw DISA STIG profile includes a rule that removes the NOPASSWD tag from every sudoers entry, so that sudo always demands re-authentication. This build deliberately excludes that rule, and its two closest siblings, from automated remediation. Here is why.

This image, like every SecureAnyCloud image, has no password on any account. Access is entirely by SSH key. If the NOPASSWD-removal rule had been applied, sudo would have demanded a password that does not exist, on the very account Azure creates for you. That would permanently lock you out of root with no recovery path, not even a console password.

That is not "requiring re-authentication". It is removing administrative access outright, so the rule is excluded.

Confirm it for yourself:

sudo -n true && echo "sudo works, no password prompt" grep -A1 'User rules for azureuser' /etc/sudoers.d/90-cloud-init-users

Expected output:

sudo works, no password prompt
# User rules for azureuser azureuser ALL=(ALL) NOPASSWD:ALL

The full list of excluded rules, and the reasoning for each, ships on the image:

cat /usr/share/sac/stig/skip-rules.txt

Expected output:

xccdf_org.ssgproject.content_rule_aide_use_fips_hashes xccdf_org.ssgproject.content_rule_disallow_bypass_password_sudo xccdf_org.ssgproject.content_rule_fips_crypto_subpolicy xccdf_org.ssgproject.content_rule_fips_custom_stig_sub_policy xccdf_org.ssgproject.content_rule_grub2_password xccdf_org.ssgproject.content_rule_sudo_remove_no_authenticate xccdf_org.ssgproject.content_rule_sudo_remove_nopasswd xccdf_org.ssgproject.content_rule_sysctl_crypto_fips_enabled

There are three exclusion groups, eight rules in total. Each is explained here:

Rule group Why it is excluded

sudo_remove_nopasswd, sudo_remove_no_authenticate, disallow_bypass_password_sudo Would strip passwordless sudo from your SSH-key-authenticated account, as above. There is no password anywhere on this image to re-authenticate with.

grub2_password The upstream remediation script for this rule reads a boot loader password interactively from a terminal. Run non-interactively during an automated build, it would hang the build indefinitely rather than fail cleanly. It has no effect on normal boot either way: GRUB password protection guards editing boot entries at the console, not booting the default one. Set it yourself with grub2-setpassword if you want that protection (see Step 7).

fips_crypto_subpolicy, fips_custom_stig_sub_policy, sysctl_crypto_fips_enabled, aide_use_fips_hashes Full FIPS 140 mode. Enabling it correctly needs fips-mode-setup --enable, a dracut -f initramfs rebuild and a further reboot to prove that it boots that way. This build does not perform that cycle, so FIPS is not enabled on this image.

4

Read the compliance evidence

The build-time compliance report ships on the image. It is generated after a reboot, so it reflects a real running boot, not a snapshot taken mid-build:

cat /var/log/sac/stig/build-remediation-score.txt ls -la /var/log/sac/stig/ oscap info --profiles /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml | grep stig

Expected output. The exact score varies from build to build as the upstream STIG content and the base image evolve. This is the measurement from this build, not a fixed target:

94.4 build-remediation-report.html build-remediation-results.xml build-remediation-score.txt xccdf_org.ssgproject.content_profile_stig:DISA STIG for AlmaLinux OS 9

Copy the HTML report to your workstation to read it properly:

echo "from your workstation: scp azureuser@<vm-ip>:/var/log/sac/stig/build-remediation-report.html ."

This profile is AlmaLinux-native, not a RHEL 9 substitute. AlmaLinux's own scap-security-guide package ships ssg-almalinux9-ds.xml with a DISA STIG for AlmaLinux OS 9 profile built in. The image resolves and probes that datastream directly. It does not assume a particular filename or fall back to a different distribution's content.

What the remaining gap is made of. This build measures in the mid-90s percent, out of 428 evaluated rules. Every rule that does not pass falls into one of these buckets:

Bucket Example rules Why it does not pass

Single-disk architecture partition_for_tmp, partition_for_var, partition_for_var_log, partition_for_var_log_audit, partition_for_var_tmp, partition_for_home These require /tmp, /var, /var/log, /var/log/audit, /var/tmp and /home on their own separate partitions. This is a single OS disk cloud image; there is nowhere for a second partition to live. These rules are not applicable to this image, and they are not defects.

The three excluded rule groups from Step 3, plus their crypto-policy cascades grub2_admin_username, selinux_context_elevation_for_sudo, configure_crypto_policy, harden_sshd_ciphers_openssh_conf_crypto_policy, harden_sshd_ciphers_opensshserver_conf_crypto_policy, harden_sshd_macs_openssh_conf_crypto_policy, harden_sshd_macs_opensshserver_conf_crypto_policy Deliberately excluded (grub2/sudo), or tied to the deferred FIPS crypto sub-policy. The system-wide crypto policy reaches its strictest FIPS-aligned state only once FIPS mode itself is enabled, which this build does not do (see Step 3).

The zero-password, key-only design require_singleuser_auth, accounts_password_all_shadowed_sha512 Rescue/single-user mode authentication and password-hash checks both assume an account has a password. No account on this image has one, because access is by SSH key only, by design.

Environment specific, no automated fix, or a package this image does not carry network_configure_name_resolution, chronyd_configure_local_socket, accounts_authorized_local_users, sysctl_user_max_user_namespaces_no_remediation, package_cron_installed, postfix_prevent_unrestricted_relay, sssd_enable_certmap, sssd_enable_smartcards, configure_usbguard_auditbackend These need a site-specific answer (your own DNS servers, your own authorized-user list), SSG itself ships no automated fix and expects manual review, or the rule hardens a package (cron, postfix, usbguard, domain-joined sssd) that is not installed on this lean base image at all.

Read the HTML report for the full rule-by-rule detail.

5

Confirm the release and patch level

cat /etc/almalinux-release grep -E '^(NAME|VERSION|ID|PLATFORM_ID)=' /etc/os-release uname -rm sudo dnf check-update; echo "check-update exit code: $?"
sudo dnf needs-restarting -r; echo "needs-restarting exit code: $?"

Expected output:

AlmaLinux release 9.8 (Olive Jaguar) NAME="AlmaLinux" VERSION="9.8 (Olive Jaguar)" ID="almalinux" PLATFORM_ID="platform:el9" 5.14.0-687.36.1.el9_8.x86_64 x86_64 check-update exit code: 0 needs-restarting exit code: 0

dnf check-update returns exit code 100 when updates are pending and 0 when none are. needs-restarting -r returns 0 when nothing requires a restart.

6

Unattended updates are already armed

systemctl is-enabled dnf-automatic.timer
systemctl is-active dnf-automatic.timer grep -E '^(upgrade_type|download_updates|apply_updates)' /etc/dnf/automatic.conf

Expected output:

enabled active upgrade_type = default download_updates = yes apply_updates = yes

upgrade_type is deliberately default rather than security. The security-only mode resolves against errata metadata. Where that metadata is not published, a security-only policy silently resolves to nothing and applies no updates at all. default applies every available update, so it cannot quietly do nothing.

7

Verify the hardening yourself

Confirm SELinux, the root account, the SSH ciphers and the boot loader kernel arguments that the STIG requires:

getenforce grep -E '^[[:space:]]*SELINUX=' /etc/selinux/config
sudo passwd -S root
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|maxauthtries|clientaliveinterval)' grep -o 'init_on_free=1\|pti=on\|vsyscall=none\|page_poison=1\|audit=1\|audit_backlog_limit=[0-9]*' /proc/cmdline

Expected output:

Enforcing SELINUX=enforcing root LK 2009-12-22 -1 -1 -1 -1 (Alternate authentication scheme in use.) permitrootlogin no passwordauthentication no kbdinteractiveauthentication no maxauthtries 6 clientaliveinterval 600 init_on_free=1 pti=on vsyscall=none page_poison=1 audit=1 audit_backlog_limit=8192

Every one of these kernel arguments is also written into /etc/default/grub, not only applied to the current boot entry. They carry forward the next time a kernel update installs a new boot entry:

grep CMDLINE_LINUX /etc/default/grub

To add password protection for editing the GRUB boot menu, use the block below. Step 3 explains why this build does not set one automatically:

sudo grub2-setpassword
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Do not run that block blindly. It sets an interactive password, so review it first.

8

Verify Azure integration survived the hardening

STIG hardening must not break the cloud-init and Azure agent paths that provision your VM. Confirm that they still work:

systemctl is-active waagent
systemctl is-enabled waagent cloud-init --version cloud-init status --long df -h / free -h

free -h reports 0B of swap, deliberately. Azure manages swap on the ephemeral resource disk through waagent rather than on the OS disk, and a swap file baked into a Marketplace image fails certification.

9

Networking and the STIG-configured firewall

Unlike the plain AlmaLinux 9 image, STIG remediation installs and enables firewalld. Confirm what it allows before you rely on it:

firewall-cmd --state firewall-cmd --get-default-zone firewall-cmd --list-services ss -tlnu

Expected output:

running public cockpit dhcpv6-client ssh Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* tcp LISTEN 0 128 [::]:22 [::]:*

ssh is explicitly permitted in the default zone. The build asserts this every time rather than assuming it, because a default-deny firewall that did not keep SSH open would lock out every customer, as the sudo rule in Step 3 would have.

cockpit and dhcpv6-client are the stock public zone's other pre-authorised services. Neither is installed or listening, so they grant nothing extra.

The Azure Network Security Group is still the first boundary your traffic crosses. With firewalld also active, a port must be open in both the NSG and firewalld before traffic reaches your service. For example:

sudo firewall-cmd --permanent --add-port=443/tcp sudo firewall-cmd --reload

rpcbind is disabled on this image, exactly as on the plain AlmaLinux 9 build. NFSv4, the default in AlmaLinux 9, does not need it. If you specifically need NFSv3, re-enable it with sudo systemctl enable --now rpcbind.socket.

The STIG's package_nfs-utils_removed rule also removes the nfs-utils package outright to reduce attack surface. If you need any NFS client capability, install it again with sudo dnf install -y nfs-utils.

10

Re-scan on demand and review the scheduled re-verification

The scanner and policy content ship on the image, so you never depend on SecureAnyCloud's own report. Check what is installed:

oscap ds sds-validate /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml && echo "datastream valid" systemctl list-timers stig-verify.timer --no-legend test -f /var/lib/sac/data/stig-firstboot.done && echo "firstboot completed: $(stat -c %y /var/lib/sac/data/stig-firstboot.done)"

stig-verify.timer runs an audit-only re-scan of your own instance a few minutes after every boot, and weekly after that. It writes a fresh report to /var/log/sac/stig/verify-report.html, verify-results.xml and verify-score.txt. It never remediates, so a scheduled job never silently changes your running configuration.

To run a full scan by hand at any time:

sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_stig \
  --results /tmp/my-scan-results.xml --report /tmp/my-scan-report.html \
  /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml echo "exit code: $? (0 = every rule passed, 2 = some rules failed -- normal, 1 = a genuine scanner error)"
11

Review and apply further remediation deliberately

The upstream bash and Ansible remediation content for every profile ships alongside the scanner. Remediation is never automatic, so you can review it and apply further hardening deliberately:

ls /usr/share/xml/scap/ssg/content/*.xml | head -3

Generating and applying additional fixes can be destructive, so review them first. They can disable services, tighten sshd further, or change mount options. The block below is a reference, not something to paste and run:

sudo oscap xccdf generate fix --profile xccdf_org.ssgproject.content_profile_stig \
  --fix-type bash /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml > /tmp/my-fixes.sh less /tmp/my-fixes.sh
# review every line before running anything

Common Use Cases

  • A pre-hardened, RHEL-compatible base for regulated or government-adjacent workloads that need DISA STIG controls applied and evidenced
  • A measured starting point for a compliance programme, with a real report instead of a checklist claim
  • A lean, patchable foundation for production servers where both security posture and provable evidence of it matter

Troubleshooting

Cannot connect via SSH

  • Verify that the VM is in the Running state in the Azure Portal.
  • Check that port 22 is allowed in the Network Security Group.
  • Make sure you are using the correct username: azureuser.
  • Verify that your SSH key matches the one you supplied at create time. Password login is disabled by design.

To see which key your client is actually offering, run ssh -v azureuser@<vm-ip> from your workstation and look for the Offering public key lines.

sudo asks for a password

It should not. If it does, something on your own VM changed the sudoers configuration after boot. This image ships with azureuser ALL=(ALL) NOPASSWD:ALL and asserts it on every build (Step 3). Check sudo -l and /etc/sudoers.d/90-cloud-init-users.

Azure agent not running

systemctl is-active waagent

If the agent is not running, start it. Run this on the VM:

sudo systemctl enable --now waagent

A service is blocked and the logs mention SELinux

sudo ausearch -m AVC -ts recent 2>/dev/null || echo "no recent AVC denials"

Prefer semanage fcontext + restorecon, or setsebool -P for the specific boolean, over disabling SELinux.

Checking for failed services

systemctl is-system-running
systemctl list-units --state=failed --no-legend

sssd.service is disabled on this image. The STIG's certificate-mapping rule writes a partial SSSD config with no domain configured, which would otherwise crash-loop sssd.service on every boot. SSSD is not needed for SSH key access. Re-enable it once you have a real domain/LDAP config in /etc/sssd/sssd.conf.

I need NFS

sudo dnf install -y nfs-utils
sudo systemctl enable --now rpcbind.socket
# only if you specifically need NFSv3

Important Notes

AlmaLinux is free and open-source software, and it is a collection of licences rather than a single one. The 571 installed packages on this image declare a wide spread of licence strings, most commonly GPLv2+, LGPLv2+, MIT, GPL+ or Artistic, BSD and GPLv3+:

rpm -qa --qf '%{LICENSE}\n' | sort | uniq -c | sort -rn | awk 'NR<=8'

The SCAP Security Guide content that ships on this image (the DISA STIG profile and every other bundled profile) is licensed BSD-3-Clause, Copyright (c) 2012-2017 Red Hat, Inc. and the ComplianceAsCode contributors, which is fully permissive. The oscap scanner itself is LGPL-2.1-or-later. Full text: /usr/share/doc/sac/SSG-BSD-3-CLAUSE-LICENSE.txt and /usr/share/doc/openscap-scanner/copyright.

No subscription, licence key or support contract is required for any of it.

"AlmaLinux" and "DISA STIG" are used here to identify the distribution and the benchmark this image implements. This listing is not affiliated with, endorsed by, or certified by the AlmaLinux OS Foundation, Red Hat, the ComplianceAsCode project, or the US Defense Information Systems Agency. A passing measured score is not a compliance certification.

Support

For assistance with this image, contact SecureAnyCloud support:

  • Website: secureanycloud.com
  • Product catalogue: secureanycloud.com/products
  • User guides: secureanycloud.com/guides
  • SLA: 24-hour response guaranteed

Deploy on Azure

Launch AlmaLinux 9 STIG-Hardened on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure