Deployment guide

Rocky Linux 9 STIG

Overview

Rocky Linux 9 STIG-Hardened is a pre-hardened virtual machine image from SecureAnyCloud, available on the Azure Marketplace. This guide covers deploying it on Microsoft Azure and verifying the result.

Rocky Linux is a community-driven enterprise Linux distribution, built by the Rocky Enterprise Software Foundation to be binary compatible with Red Hat Enterprise Linux 9. This edition applies the DISA STIG (Security Technical Implementation Guide) profile from the SCAP Security Guide project 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 with no remediation flag to produce a real, 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 system. It is 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:

  • Rocky Linux 9, binary compatible with RHEL 9, with the DISA STIG profile applied
  • 601 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, with 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. This is a deliberate, documented exception (see Step 3).
  • Azure Linux Agent (waagent) and cloud-init for Azure integration, verified 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 "Rocky Linux 9 STIG-Hardened SecureAnyCloud".
  • Select the image and click Create.
  • Configure the basics:
  • Subscription: select your Azure subscription.
  • Resource Group: create a new group or select an existing one.
  • Virtual Machine Name: enter a name for your VM.
  • Region: select your preferred Azure region.
  • Size: Standard_B2s is 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:rocky-9-stig:default:latest
az vm create \
  --resource-group myResourceGroup \
  --name my-rocky-9-stig-vm \
  --image sac1647283583153:rocky-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-rocky-9-stig-vm --show-details --query publicIps -o tsv ssh azureuser@<vm-ip>

There is no password to enter. If SSH asks 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. The connection model is the same as on 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. The reason is as follows.

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. It would not require re-authentication. It would remove administrative access outright.

To confirm the configuration, run:

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

The eight excluded rules fall into three groups:

  • sudo_remove_nopasswd, sudo_remove_no_authenticate, disallow_bypass_password_sudo: these would strip passwordless sudo from your SSH-key-authenticated account, as described 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. The rule has no effect on normal boot, because 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: these enable full FIPS 140 mode. Enabling it correctly needs fips-mode-setup --enable, a dracut -f initramfs rebuild and a further reboot to prove the system 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 was 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-rl9-ds.xml | grep stig

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

94.9 build-remediation-report.html build-remediation-results.xml build-remediation-score.txt xccdf_org.ssgproject.content_profile_stig:DISA STIG for Red Hat Enterprise Linux 9

To read the report properly, copy the HTML file to your workstation:

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

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:

  • 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 rules 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, so there is nowhere for a second partition to live. The rules are not applicable here, and this is not a defect.
  • The three excluded rule groups from Step 3 (the crypto and FIPS rules and the sudo-nopasswd rules): these are deliberately excluded. They cascade into a couple of closely related checks, such as grub2_admin_username (tied to the boot loader password) and selinux_context_elevation_for_sudo.
  • Environment-specific rules with no automated fix (network_configure_name_resolution, chronyd_server_directive, sssd_enable_certmap, installed_OS_is_vendor_supported, accounts_authorized_local_users, sysctl_user_max_user_namespaces_no_remediation): these need a site-specific answer, such as your own DNS servers, your own NTP servers or your own authorized-user list. For the "no_remediation" rule, SSG itself ships no automated fix and expects manual review.

None of these rules is hidden or remediated away. Read the HTML report for the full rule-by-rule detail.

5

Confirm the release and patch level

cat /etc/rocky-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:

Rocky Linux release 9.8 (Blue Onyx) NAME="Rocky Linux" VERSION="9.8 (Blue Onyx)" ID="rocky" 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. The default mode applies every available update, so it cannot quietly do nothing.

7

Verify the hardening yourself

Confirm SELinux, root, the SSH settings 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 therefore carry forward when a kernel update installs a new boot entry:

grep CMDLINE_LINUX_DEFAULT /etc/default/grub

To add password protection for editing the GRUB boot menu, run the following. Step 3 explains why this build does not set a password automatically.

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

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

8

Verify Azure integration survived the hardening

The most important property of this image is that STIG hardening does not break the cloud-init and Azure agent paths that provision your VM. Confirm it:

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, by design. 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 Rocky Linux 9 image, this image has STIG remediation install and enable 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. This is asserted on every build, not assumed, because a default-deny firewall that did not keep SSH open would lock out every customer, as the sudo issue in Step 3 would have. cockpit and dhcpv6-client are the other pre-authorised services in the stock public zone. 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:

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

rpcbind is disabled on this image, as on the plain Rocky Linux 9 build. NFSv4, the default in Rocky Linux 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 scheduled re-verification

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

oscap ds sds-validate /usr/share/xml/scap/ssg/content/ssg-rl9-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, writing a fresh report to /var/log/sac/stig/verify-report.html, verify-results.xml and verify-score.txt. It never remediates, because this image does not run a scheduled job that could silently change 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-rl9-ds.xml echo "exit code: $? (0 = every rule passed, 2 = some rules failed -- normal, 1 = a genuine scanner error)"
11

Review remediation before you apply it

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

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

Applying additional fixes can be destructive, so review them first. They can disable services, tighten sshd further or change mount options. The next block 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-rl9-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.
  • Use the correct username: azureuser.
  • Verify 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. This image ships with azureuser ALL=(ALL) NOPASSWD:ALL and asserts it on every build (Step 3), so a password prompt means something on your VM changed the sudoers configuration after boot. 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 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"

Do not disable SELinux. Use semanage fcontext with restorecon, or setsebool -P for the specific boolean.

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 configuration with no domain, 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 or LDAP configuration 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

Rocky Linux is free and open source, and it is a collection of licences rather than a single one. The 601 installed packages on this image declare 126 distinct licence strings, most commonly LGPLv2+, GPLv2+, 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. The full text is in /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. "Rocky Linux" and "DISA STIG" identify the distribution and the benchmark this image implements. This listing is not affiliated with, endorsed by, or certified by the Rocky Enterprise Software Foundation, Red Hat, the ComplianceAsCode project, or the US Defense Information Systems Agency. A passing measured score is not a compliance certification.

Support

For help 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 Rocky Linux 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