Operating Systems AWS

Ubuntu Server 24.04 on AWS User Guide

| Product: Ubuntu Server 24.04 LTS on AWS

Overview

This image is Ubuntu Server 24.04 LTS (Noble Numbat), one software version of the cloudimg Hardened Ubuntu 22.04 & 24.04 LTS AMIs: Minimal, LVM, STIG listing on AWS Marketplace. Every release and variant in the listing is described at /guides/ubuntu-server-aws/.

Host firewall enabled (ufw, inbound SSH only): open app ports in both the security group and ufw. Opening a port in the security group alone is not enough.

What cloudimg does to the image:

  • Remediated with the OpenSCAP CIS Level 1 Server profile. The build applies the SCAP Security Guide 0.1.82 profile cis_level1_server with oscap --remediate, reboots, then runs a scored evaluation. The build fails if fewer than 95% of the scored rules pass; this version scored 345 of 352 rules (98.0%). The report ships in the image at /usr/share/doc/cloudimg/cis-l1-report.html. This is a measurement made by open-source tooling, not a certification or accreditation by the benchmark's publisher or any other body, and these images are not CIS Hardened Images.
  • 6 rules waived, each with its reason (listed below and in /usr/share/doc/cloudimg/cis-l1-waivers.txt): rules that would lock you out of an EC2 instance or need a password baked into every copy of the image.
  • Fully patched at build time. The build fails if any update is outstanding or a reboot is pending.
  • Automatic security updates stay on (unattended-upgrades).
  • Key-only SSH as ubuntu. Password login is off, root login is refused, the root account is locked and no account has a password.
  • IMDSv2 only. The AMI is marked IMDSv2-only, so instances launched from it require session tokens for instance metadata.
  • Host firewall on (ufw): inbound SSH only, outbound open so the SSM agent, updates, DNS and NTP work.
  • AppArmor enforcing, chrony on the Amazon Time Sync Service (169.254.169.123, no internet access needed), and the AWS Systems Manager (SSM) agent installed from its Debian package (no snaps).
  • Nothing shared between instances. No SSH keys, host keys, machine ID or credentials are baked in; cloud-init creates them on each instance's first boot.
  • No swap, and SSH is the only service listening on the network (the DNS stub resolver, chrony and the local mail transfer agent listen on loopback only).

Connecting to your instance

Connect over SSH as the default login user for the operating system variant you launched:

Operating system variant SSH login user
Ubuntu Server 24.04 LTS ubuntu

Launch from AWS Marketplace

  1. Open the listing on AWS Marketplace, choose Continue to Subscribe, then Continue to Configuration.
  2. In Software version, choose Ubuntu Server 24.04 LTS - YYYY-MM-DD (the date is when the version was added).
  3. Choose your Region, Continue to Launch, then Launch through EC2.
  4. Instance type: m5.large is recommended; any current x86_64 type works. The software charge is $0.04 per vCPU per hour.
  5. Key pair: choose your EC2 key pair. It is the only way in. RSA and ED25519 keys both work.
  6. Security group: allow inbound TCP 22 from your own IP address or network only.
  7. Metadata: leave the defaults. The image already requires IMDSv2.

From the AWS CLI (use the AMI ID the Marketplace launch page shows for your Region):

aws ec2 run-instances --image-id ami-xxxxxxxxxxxxxxxxx --instance-type m5.large \
  --key-name my-key --security-group-ids sg-xxxxxxxx --subnet-id subnet-xxxxxxxx

Connect over SSH

ssh -i ~/.ssh/my-key.pem ubuntu@<public-ip>

ubuntu has passwordless sudo. There is no password on any account. AWS Systems Manager Session Manager also works: the SSM agent is installed and running (attach an instance profile with AmazonSSMManagedInstanceCore).

Confirm the release and kernel:

grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME)=' /etc/os-release
uname -r
PRETTY_NAME="Ubuntu 24.04.5 LTS"
VERSION_ID="24.04"
VERSION_CODENAME=noble
7.0.0-1014-aws

The kernel version moves forward as Ubuntu publishes updates; the image ships the kernel current on its build date.

Ubuntu Server 24.04 release, kernel and IMDSv2 check on an EC2 instance

IMDSv2

The AMI carries ImdsSupport=v2.0, so every instance launched from it gets HttpTokens=required. Check it on the instance:

imds_token=$(curl -s -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 300' http://169.254.169.254/latest/api/token)
curl -s -H "X-aws-ec2-metadata-token: $imds_token" http://169.254.169.254/latest/meta-data/instance-id; echo
curl -s -o /dev/null -w '%{http_code}\n' http://169.254.169.254/latest/meta-data/instance-id

The first request (with a token) returns your instance ID; the second (IMDSv1, no token) is refused with 401:

i-0123456789abcdef0
401

Software that still uses IMDSv1 must be updated to request a token (current AWS SDKs and CLIs already do). If you must allow IMDSv1 temporarily, change it per instance with aws ec2 modify-instance-metadata-options --http-tokens optional; we do not recommend it.

View the compliance report

The evaluation the build ran is in /usr/share/doc/cloudimg/cis-l1-report.html (HTML), /usr/share/doc/cloudimg/cis-l1-arf.xml (machine-readable ARF) and /usr/share/doc/cloudimg/cis-l1-summary.json (counts, pass rate, threshold, SSG version and checksums). The build host's name and addresses are replaced in the shipped copy.

ls /usr/share/doc/cloudimg/
python3 -c "import json; d=json.load(open('/usr/share/doc/cloudimg/cis-l1-summary.json')); print(d['profileLabel'], d['ssgVersion'], '-', d['pass'], 'of', d['scored'], 'scored rules pass', '(' + str(d['ratePct']) + '%)', '- waived', d['waived'])"
cis-l1-arf.xml  cis-l1-report.html  cis-l1-summary.json  cis-l1-tailoring.xml  cis-l1-waivers.txt  scap-security-guide-LICENSE.txt
CIS Level 1 Server 0.1.82 - 345 of 352 scored rules pass (98.0%) - waived 6

The shipped OpenSCAP CIS Level 1 Server evaluation summary

Copy the HTML report to your workstation and open it in a browser:

scp -i ~/.ssh/my-key.pem ubuntu@<public-ip>:/usr/share/doc/cloudimg/cis-l1-report.html .

The 7 scored rules that do not pass are listed in the summary's failingRules: all_apparmor_profiles_in_enforce_complain_mode, permissions_local_var_log, root_path_all_dirs, set_nftables_loopback_traffic, set_nftables_table, set_ufw_default_rule and ufw_rules_for_open_ports. The two nftables rules belong to the profile's nftables firewall option, which this image does not use (ufw is the host firewall), and set_ufw_default_rule fails because outbound traffic is allowed on purpose (see below).

Re-run the same evaluation on your instance at any time (about 5 to 10 minutes; nothing is changed):

sudo oscap xccdf eval --tailoring-file /usr/share/cloudimg/openscap/tailoring.xml \
  --profile xccdf_co.cloudimg_profile_cis-l1_ec2_single \
  --report ~/cis-l1-rescan.html /usr/share/xml/scap/ssg/content/ssg-ubuntu2404-ds.xml

oscap exits with status 2 when any rule fails; that is expected and the HTML report is still written.

Waived rules

These rules of the profile are deselected through the tailoring file /usr/share/cloudimg/openscap/tailoring.xml and are not scored. Everything else the profile selects is remediated and scored. The tailoring also selects ufw as the profile's firewall option and chrony as its time service.

Rule Category Why it is waived on EC2
partition_for_tmp single-volume-partitions The standard and Minimal versions boot from one EBS root volume, so /tmp, /var, /var/log, /var/log/audit, /var/tmp and /home live on the root filesystem. The LVM versions provide every one of these as a separate volume with the benchmark's mount options.
grub2_password serial-console-and-boot A boot-loader or root password would have to be baked into the image, giving every instance the same credential. Root stays locked and no password exists. EC2 Serial Console and single-user recovery need a password you set yourself on your own instance (see Emergency access).
grub2_uefi_password serial-console-and-boot As above, for the UEFI boot loader.
accounts_maximum_age_login_defs key-only-account-aging Password-age and inactivity defaults apply to every account cloud-init creates at launch. Those accounts are key-only and have no password, so an age limit expires them and locks the SSH key out. Accounts that do have a password are still aged.
account_disable_post_pw_expiration key-only-account-aging As above, for the inactivity default.
sshd_limit_user_access ssh-access-list An AllowUsers/AllowGroups list fixed at build time would refuse SSH to accounts you create with cloud-init user-data. SSH is key-only, root login is refused, and your security group restricts the source addresses.

Deliberate deviation (scored as a failure): ufw denies all inbound traffic except SSH (22/tcp) but allows outbound traffic, so set_ufw_default_rule reports fail and counts against the score. A default-deny outbound policy would cut the SSM agent off from its endpoints, cloud-init and the instance from the metadata service (169.254.169.254), and the instance from DNS, NTP and package updates.

Host firewall and SSH

ufw is active and allows inbound SSH only; SSH accepts keys only:

sudo ufw status verbose
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|pubkeyauthentication) '
sudo passwd -S root
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
Anywhere on lo             ALLOW IN    Anywhere
22/tcp                     ALLOW IN    Anywhere
Anywhere                   DENY IN     127.0.0.0/8
Anywhere (v6) on lo        ALLOW IN    Anywhere (v6)
22/tcp (v6)                ALLOW IN    Anywhere (v6)
Anywhere (v6)              DENY IN     ::1

Anywhere                   ALLOW OUT   Anywhere on lo
Anywhere (v6)              ALLOW OUT   Anywhere (v6) on lo

permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
root L 2024-04-23 0 99999 7 -1

root L means the root password is locked.

ufw allowing inbound SSH only, and key-only SSH settings

To serve anything else, open the port in both your security group and the host firewall, for example HTTPS:

sudo ufw allow 443/tcp
sudo ufw status verbose

Services

systemctl is-active ssh ufw apparmor amazon-ssm-agent chrony
systemctl is-enabled unattended-upgrades
chronyc -n sources | head -3
active
active
active
active
active
enabled
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* 169.254.169.123               3   4   377    11    -35us[  -45us] +/-  520us

^* marks the source chrony is synchronised to: the Amazon Time Sync Service at 169.254.169.123, which every EC2 instance reaches without internet access. The Ubuntu NTP pool servers in /etc/chrony/chrony.conf are kept as fallbacks.

Patching

unattended-upgrades installs security updates automatically. To see how many updates are waiting, and to install everything now:

apt list --upgradable 2>/dev/null | grep -c upgradable || true
sudo apt-get update && sudo apt-get -y upgrade

If /var/run/reboot-required exists afterwards, a new kernel or core library was installed: reboot when convenient with sudo reboot.

Emergency access

There is no password on any account, by design. EC2 Serial Console and single-user recovery need a local password: set one for root on your own instance before you need it, and keep it in your secrets store:

sudo passwd root

Session Manager (SSM) is the other way in if SSH is unavailable.

Support

cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. Ubuntu is a registered trademark of Canonical Ltd. CIS and CIS Benchmarks are trademarks of the Center for Internet Security, Inc. cloudimg is not affiliated with or endorsed by Canonical or CIS.