AWS EC2: Automated setup for disabling NOPASSWD sudo

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:

Parameter Store

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.

Parameter Details

The new parameter should now be visible:

Parameter Store

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.