Problem
If you setup an AWS EC2 instance using one of the default operating systems
(such as Amazon Linux 2023 or Ubuntu), you’ll notice that the root user and
the default user (ec2-user or ubuntu) are configured with an empty password.
On top of this, the /etc/sudoers.d/90-cloud-init-users file contains the following
configuration, allowing password-less sudo access for the ec2-user:
# Created by cloud-init v. 22.2.2 on Fri, 10 May 2024 09:56:37 +0000
# User rules for ec2-user
ec2-user ALL=(ALL) NOPASSWD:ALL
This doesn’t look very good.
To be fair, the default setup uses SSH as the only ingress channel. SSH is
configured with PasswordAuthentication no to enforce public key authentication
by default. This reduces the security implications from a password-less ec2-user
and root setup.
However, if the same server is to host other internet-accessible services, such
as a web server, the password-less sudo can be a big issue since any intruder
could find a way to the ec2-user, and eventually, the root access with few
impediments in his privesc attempts.
Solution
Going for a full-fledged Identity Management (IdM) solution was a bit of an
overkill for my use case. I settled with using Parameter Store provided by
AWS System Manager to store the hashes for the user and root passwords. And
then, I used AWS CLI to fetch those hashes and set the new passwords
using usermod command through user-data.sh at boot time. Here are the details:
Step 1: Prepare password hashes for root and default user
You can do this interactively on your shell. We will generate a duly-formatted
hashed string for myverysecretpassword
We can use the venerable tool, openssl, to generate a random salt and then
generate the SHA512 hash for the required password:
$ openssl passwd -6 -salt $(openssl rand -base64 32) myverysecretpassword
The output would look something like this:
$6$e1OaNL+aC6sKpczH$1p8NKTfaP33ABOjxfV6tC5loMbLTygSBf5IU/Mukngie3XNP3eMrIO/iiTs4Jz5X4MHPoGwKqSMLVxaXI73xB0
This string might look familiar, since this is exactly how Linux stores password
hashes in the /etc/shadow file. To understand this string format you can use
man 5 crypt.
Step 2: Store the password hashes in Parameter Store
On the AWS Management Console, browse to AWS Systems Manager -> Parameter Store, and then click on Create Parameter:

Set a name for the new parameter, and then paste the hash string from Step 1
in the Value field. Hit Create Parameter when done.

The new parameter should now be visible:

Repeat Step 1 and Step 2 to create a separate hash string for the user
account as well.
Step 3: Configure user-data.sh to fetch and set the new passwords
We can use AWS CLI in the user-data.sh script to fetch the parameters, and
then use usermod to set the password. Here’s a short bash script to perform
these steps:
# Fetch the hashed password strings from the Parameter Store
root_pass=$(aws ssm get-parameters --names root_pass --query "Parameters[0].Value" --output text)
user_pass=$(aws ssm get-parameters --names user_pass --query "Parameters[0].Value" --output text)
# Set root password
usermod --password "$root_pass" root
# This code sets the user password based
# on whether we are on AL2023 or ubuntu
if command -v apt-get >/dev/null 2>&1; then
usermod --password "$user_pass" ubuntu
elif command -v yum >/dev/null 2>&1; then
usermod --password "$user_pass" ec2-user
fi
Step 4: Disable NOPASSWD sudo for the default user
A simple sed can take care of this:
# Disable passwordless sudo
sed -i '/^\s*#/!s/^/#/' /etc/sudoers.d/90-cloud-init-users
Result
After this setup, all of our EC2 instances would have a password enabled for the root account and the default user account. The password-less sudo would also be disabled. Both these steps would considerably increase the privesc time.
The parameter stored in the Parameter Store can be updated every X days, depending on the perceived threat environment.