Deployment guide

Ubuntu 22.04 STIG

Overview

This guide shows you how to deploy and verify Ubuntu 22.04 LTS STIG-Hardened on Microsoft Azure. You deploy SecureAnyCloud's pre-configured virtual machine image from the Azure Marketplace.

Ubuntu 22.04 LTS is Canonical's widely deployed long-term support release. This edition applies the DISA STIG (Security Technical Implementation Guide) profile for Ubuntu 22.04 at build time. The profile comes from the open source SCAP Security Guide (ComplianceAsCode) project and is applied with oscap xccdf eval --remediate, the same open source engine you can run yourself.

The build then reboots the machine and re-scans it with no remediation flag, which produces a real measured compliance score. That score, the full HTML report and the machine-readable results all ship on the image.

This image does not use Canonical's USG tool or an Ubuntu Pro subscription. Canonical's usg fix --profile disa_stig is the commercial path to the same benchmark. It requires an active Ubuntu Pro subscription and token (sudo pro enable usg). This build uses the free, redistributable SCAP Security Guide content directly. No extra subscription, token or support contract is attached to the compliance work.

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 for Canonical Ubuntu 22.04 LTS. 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:

  • Ubuntu 22.04 LTS with the DISA STIG profile (V2R7) applied, from the open source SCAP Security Guide
  • 688 installed packages on a fresh build, including the SCAP Security Guide content and the auditd and AppArmor tooling the STIG profile relies on
  • No Ubuntu Pro subscription, USG or any commercial entitlement anywhere in the build or the image
  • 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 upgrades already armed
  • AppArmor enabled and enforcing, both at runtime and armed for the next boot
  • Passwordless sudo for your SSH-key-authenticated account. This is a deliberate, documented exception (see Step 3)
  • Azure Linux Agent (walinuxagent) 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 "Ubuntu 22.04 LTS 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:ubuntu-22-04-stig:default:latest
az vm create \
  --resource-group myResourceGroup \
  --name my-ubuntu-22-04-stig-vm \
  --image sac1647283583153:ubuntu-22-04-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-ubuntu-22-04-stig-vm --show-details --query publicIps -o tsv ssh azureuser@<vm-ip>

There is no password to enter. If SSH asks for one, your client is not offering the key you supplied at create time. The STIG sshd hardening tightens session timeouts and auth-try limits (see Step 7). 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 rules (sudo_remove_nopasswd, sudo_remove_no_authenticate) that remove the NOPASSWD tag from every sudoers entry, so that sudo always demands re-authentication. This build deliberately excludes those rules from automated remediation. The reason is simple.

This image, like every SecureAnyCloud image, has no password on any account. Access is by SSH key only. If the NOPASSWD-removal rules had been applied, sudo would ask for a password that does not exist. That would permanently lock you out of root on the account Azure creates for you, with no recovery path and no console password. That is not re-authentication. It removes administrative access outright.

Confirm it yourself:

sudo -n true && echo "sudo works, no password prompt" grep -A1 'User rules for' /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 ships on the image:

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

Expected output:

xccdf_org.ssgproject.content_rule_grub2_password xccdf_org.ssgproject.content_rule_grub2_uefi_password xccdf_org.ssgproject.content_rule_is_fips_mode_enabled xccdf_org.ssgproject.content_rule_sudo_remove_no_authenticate xccdf_org.ssgproject.content_rule_sudo_remove_nopasswd

The exclusions fall into three groups, five rules in total:

Rule group Why it is excluded

sudo_remove_nopasswd, sudo_remove_no_authenticate 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, grub2_uefi_password The upstream remediation script for these rules reads a boot loader password interactively from a terminal. In an automated build it would hang indefinitely instead of failing cleanly. Skipping it does not affect normal boot. GRUB password protection guards editing boot entries at the console, not booting the default entry.

is_fips_mode_enabled Full FIPS 140 kernel mode. Enabling it correctly needs a reboot-and-verify cycle that this build does not perform. On Ubuntu, the certified FIPS kernel and crypto packages are also an Ubuntu Pro entitlement. FIPS is not enabled on this image.

None of these exclusions requires an Ubuntu Pro subscription. They are applied with the free, open source SCAP Security Guide tailoring mechanism described in Step 10.

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-ubuntu2204-ds.xml | grep -i stig

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

96.7 build-remediation-report.html build-remediation-results.xml build-remediation-score.txt xccdf_org.ssgproject.content_profile_stig:Canonical Ubuntu 22.04 LTS Security Technical Implementation Guide (STIG) V2R7

Copy the HTML report to your workstation to read it:

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 96.7% across 213 rules that were actually checked (pass or fail). The STIG profile selects 230 rules in total. The rest are informational, manual-review-only or not applicable, and they do not count for or against the score. Every rule that does not pass falls into one of these buckets:

Bucket Rules Why it does not pass

Single-disk architecture partition_for_dev_shm Wants /dev/shm on its own mount with dedicated options. This is a single-OS-disk cloud image, so there is nowhere for a second mount to live. The rule is not applicable here and is not a defect.

The excluded rule groups from Step 3 (cascading checks only) No separately failing rule beyond the five explicitly skipped. They show notselected, not fail, and are not counted in the 213.

Firewall not configured at the OS level set_ufw_default_rule, ufw_rules_for_open_ports ufw ships installed but inactive by design. The Azure Network Security Group is the network boundary for this image (see Step 9). Enable and configure ufw yourself if you also want OS-level filtering.

Environment-specific, no domain configured sssd_certification_path_trust_anchor, sssd_offline_cred_expiration These need a site-specific answer: your own certificate trust anchors and credential-caching policy for a real LDAP or AD domain. This image has no domain join.

Content limitation, verified false negative package_prelink_removed, chronyd_run_as_chrony_user Checked directly on this build: prelink is not installed, and chronyd runs as the _chrony system user (the Debian and Ubuntu naming convention, _chrony rather than chrony). Both controls are satisfied. The automated OVAL check in this content release does not correctly detect the Debian-family naming and state on Ubuntu 22.04. These rules still count in the score.

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

5

Confirm the release and patch level

cat /etc/os-release uname -rm sudo apt-get update -qq sudo apt-get -s -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade | grep -c '^Inst ' || echo 0 test -f /var/run/reboot-required && echo "reboot required" || echo "no reboot required"

Expected output:

PRETTY_NAME="Ubuntu 22.04.5 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.5 LTS (Jammy Jellyfish)" ID=ubuntu ID_LIKE=debian UBUNTU_CODENAME=jammy 6.8.0-1064-azure x86_64 0 no reboot required

APT::Get::Always-Include-Phased-Updates=true matters. Ubuntu's phased update rollout can otherwise hold back a subset of updates, including security-relevant ones, on an individual machine without reporting a failure. This image is built and verified with phased updates always included. A result of 0 therefore means fully patched.

6

Unattended upgrades are already armed

systemctl is-enabled unattended-upgrades.service
systemctl is-enabled apt-daily.timer
systemctl is-enabled apt-daily-upgrade.timer grep -E '^(APT::Periodic)' /etc/apt/apt.conf.d/20auto-upgrades

Expected output:

enabled enabled enabled APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1";

unattended-upgrades applies security updates automatically on your VM, as a stock Ubuntu image does. The STIG hardening does not disable this.

7

Verify the hardening yourself

Confirm AppArmor, the root account, the SSH policy and the STIG audit kernel argument:

aa-status --enabled && echo "AppArmor enabled" systemctl is-enabled apparmor.service
sudo passwd -S root
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|maxauthtries|clientaliveinterval)' grep -o 'audit=1' /proc/cmdline

Expected output:

AppArmor enabled enabled root L 02/09/2026 0 99999 7 -1 permitrootlogin without-password passwordauthentication no kbdinteractiveauthentication no maxauthtries 6 clientaliveinterval 600 audit=1

audit=1 is also written into /etc/default/grub's GRUB_CMDLINE_LINUX_DEFAULT, not only applied to the current boot entry. Ubuntu's update-grub reads this file on every kernel update, so the setting carries forward with no separate persistence step.

grep CMDLINE_LINUX_DEFAULT /etc/default/grub
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 both still work:

systemctl is-active walinuxagent 2>/dev/null || systemctl is-active waagent
systemctl is-enabled walinuxagent 2>/dev/null || 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, not on the OS disk. A swap file baked into a Marketplace image fails certification.

9

Networking: the Azure NSG is the firewall boundary

This image does not enable ufw by default. It ships installed but inactive. The Azure Network Security Group is already the network boundary for this VM. A second default-deny layer that failed to permit SSH would lock customers out, as the sudo issue in Step 3 would have. Confirm what is listening:

ss -tlnH | awk '{print $1, $4}'
sudo postconf -h inet_interfaces

Expected output:

tcp 127.0.0.1:25 tcp 0.0.0.0:22 tcp 127.0.0.53%lo:53 tcp [::1]:25 tcp [::]:22 loopback-only

The only externally bound listener is SSH on port 22 (IPv4 and IPv6). Postfix (local mail for cron and system tooling) and the systemd-resolved DNS stub are both loopback-only. Postfix ships on the base Ubuntu image bound to all interfaces. This build restricts it to loopback-only, so it cannot accept mail from the internet and local mail delivery for system tools keeps working.

To add OS-level filtering on top of the NSG, run:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

Review these commands before you run them. Enabling ufw without first allowing SSH can lock you out, as an NSG misconfiguration would. Keep an existing SSH session open and test access from a second session before you disconnect.

10

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

The scanner and policy content ship on the image, so you do not depend on SecureAnyCloud's own report. Validate the datastream and check the timer:

oscap ds sds-validate /usr/share/xml/scap/ssg/content/ssg-ubuntu2204-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"

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 cannot silently change your running configuration.

To run a full scan by hand, use the same tailored profile the build used. The jammy oscap predates --skip-rule, so this image ships an XCCDF tailoring file that excludes the five rules from Step 3:

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

To scan against the raw, untailored DISA STIG profile instead, run the command below. It also evaluates the five rules from Step 3, so read what they do before you run any generated fixes:

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

Review remediation before you apply it

The upstream bash and Ansible remediation content for this profile ships alongside the scanner. You can review it and apply further hardening deliberately:

ls /usr/share/sac/stig/remediation/bash | head -5

Review additional fixes before you apply them. They can disable services, tighten sshd further or change mount options. Treat the block below as a reference, not as 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-ubuntu2204-ds.xml > /tmp/my-fixes.sh less /tmp/my-fixes.sh
# review every line before running anything

Common Use Cases

  • A pre-hardened, familiar Ubuntu LTS base for regulated or government-adjacent workloads that need DISA STIG controls applied and evidenced
  • A measured starting point for a compliance program, with a real report and no Ubuntu Pro subscription to buy
  • A lean, patchable foundation for production servers where security posture and evidence of it both matter

Troubleshooting

Cannot connect via SSH

  • Confirm 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.
  • Make sure your SSH key matches the one you supplied at create time. Password login is disabled by design.

To see which key your client offers, run ssh -v azureuser@<vm-ip> from your workstation. Look for the Offering public key lines.

sudo asks for a password

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

Azure agent not running

systemctl is-active walinuxagent 2>/dev/null || systemctl is-active waagent

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

sudo systemctl enable --now walinuxagent 2>/dev/null || sudo systemctl enable --now waagent

A service is blocked and you suspect AppArmor

sudo aa-status | grep -i complain
sudo journalctl -k | grep -i apparmor | tail -20

Adjust the specific profile in /etc/apparmor.d/ and reload it with sudo apparmor_parser -r instead of disabling AppArmor.

Checking for failed services

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

If sssd.service appears in the failed list, note that it is disabled on this image when no domain is configured. SSSD is not needed for SSH key access. Enable it once you have a real domain or LDAP configuration in /etc/sssd/sssd.conf.

Add OS-level firewalling in addition to the NSG

sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status

Always allow SSH before you enable ufw. Test from a second session before you disconnect your first one.

Important Notes

Ubuntu is free and open source, and it is a collection of licenses rather than a single license. The 688 installed packages on this image declare dozens of distinct license terms, most commonly GPL-2, MIT, GPL-3, LGPL-2.1, ISC, Artistic and Apache-2.0:

for p in $(dpkg-query -W -f='${Package}\n'); do f="/usr/share/doc/$p/copyright" [ -s "$f" ] || continue grep -m1 -oE '(GPL-[0-9.]+|LGPL-[0-9.]+|MIT|BSD-[0-9]-Clause|Apache-2\.0|MPL-[0-9.]+|Artistic|ISC)' "$f" done | sort | uniq -c | sort -rn | head -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. The oscap scanner itself (shipped inside Ubuntu's libopenscap8 package) is LGPL-2.1-or-later. Full text: /usr/share/doc/sac/SSG-BSD-3-CLAUSE-LICENSE.txt and /usr/share/doc/libopenscap8/copyright.

No subscription, license key, Ubuntu Pro entitlement or support contract is required for any of it. "Ubuntu" and "DISA STIG" identify the distribution and the benchmark this image implements. This listing is not affiliated with, endorsed by, or certified by Canonical, 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 Ubuntu 22.04 LTS 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