Created
March 14, 2018 21:32
-
-
Save miguelmota/c4cf50964e4f426dc75fdddb6081f380 to your computer and use it in GitHub Desktop.
AWS bastion host monitor script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://aws.amazon.com/blogs/security/how-to-record-ssh-sessions-established-through-a-bastion-host/ | |
###################################################### | |
# Config variables | |
###################################################### | |
BUCKET_NAME="my-bastion-host" | |
###################################################### | |
# Log commands to log file | |
###################################################### | |
# Create a new folder for the log files | |
mkdir -p /var/log/bastion | |
# Allow ec2-user only to access this folder and its content | |
chown ec2-user:ec2-user /var/log/bastion | |
chmod -R 770 /var/log/bastion | |
setfacl -Rdm other:0 /var/log/bastion | |
# Make OpenSSH execute a custom script on logins | |
echo -e "\nForceCommand /usr/bin/bastion/shell" >> /etc/ssh/sshd_config | |
# Block some SSH features that bastion host users could use to circumvent | |
# the solution | |
awk '!/AllowTcpForwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config | |
awk '!/X11Forwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config | |
echo "AllowTcpForwarding no" >> /etc/ssh/sshd_config | |
echo "X11Forwarding no" >> /etc/ssh/sshd_config | |
mkdir -p /usr/bin/bastion | |
cat > /usr/bin/bastion/shell << 'EOF' | |
# Check that the SSH client did not supply a command | |
if [[ -z $SSH_ORIGINAL_COMMAND ]]; then | |
# The format of log files is /var/log/bastion/YYYY-MM-DD_HH-MM-SS_user | |
LOG_FILE="`date --date="today" "+%Y-%m-%d_%H-%M-%S"`_`whoami`" | |
LOG_DIR="/var/log/bastion/" | |
# Print a welcome message | |
echo "" | |
echo "NOTE: This SSH session will be recorded" | |
echo "AUDIT KEY: $LOG_FILE" | |
echo "" | |
# I suffix the log file name with a random string. I explain why | |
# later on. | |
SUFFIX=`mktemp -u _XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` | |
# Wrap an interactive shell into "script" to record the SSH session | |
script -qf --timing=$LOG_DIR$LOG_FILE$SUFFIX.time $LOG_DIR$LOG_FILE$SUFFIX.data --command=/bin/bash | |
else | |
# The "script" program could be circumvented with some commands | |
# (e.g. bash, nc). Therefore, I intentionally prevent users | |
# from supplying commands. | |
echo "This bastion supports interactive sessions only. Do not supply a command" | |
exit 1 | |
fi | |
EOF | |
# Make the custom script executable | |
chmod a+x /usr/bin/bastion/shell | |
# Bastion host users could overwrite and tamper with an existing log file | |
# using "script" if they knew the exact file name. I take several measures | |
# to obfuscate the file name: | |
# 1. Add a random suffix to the log file name. | |
# 2. Prevent bastion host users from listing the folder containing log | |
# files. | |
# This is done by changing the group owner of "script" and setting GID. | |
chown root:ec2-user /usr/bin/script | |
chmod g+s /usr/bin/script | |
# 3. Prevent bastion host users from viewing processes owned by other | |
# users, because the log file name is one of the "script" | |
# execution parameters. | |
mount -o remount,rw,hidepid=2 /proc | |
awk '!/proc/' /etc/fstab > temp && mv temp /etc/fstab | |
echo "proc /proc proc defaults,hidepid=2 0 0" >> /etc/fstab | |
# Restart the SSH service to apply /etc/ssh/sshd_config modifications. | |
service sshd restart | |
###################################################### | |
# Copy log files to S3 | |
###################################################### | |
# Copy log files to S3 with server-side encryption enabled. | |
# Then, if successful, delete log files that are older than a day. | |
LOG_DIR="/var/log/bastion/" | |
S3_SYNC="aws s3 cp $LOG_DIR s3://$BUCKET_NAME/logs/ --sse --recursive && find $LOG_DIR* -mtime +1 -exec rm {} \;" | |
echo $S3_SYNC > /usr/bin/bastion/sync_s3 | |
chmod 700 /usr/bin/bastion/sync_s3 | |
###################################################### | |
# Set up SSH access for users | |
###################################################### | |
# Bastion host users should log in to the bastion host with | |
# their personal SSH key pair. The public keys are stored on | |
# S3 with the following naming convention: "username.pub". This | |
# script retrieves the public keys, creates or deletes local user | |
# accounts as needed, and copies the public key to | |
# /home/username/.ssh/authorized_keys | |
echo "BUCKET_NAME=$BUCKET_NAME" > /usr/bin/bastion/sync_users | |
cat >> /usr/bin/bastion/sync_users << 'EOF' | |
# The file will log user changes | |
LOG_FILE="/var/log/bastion/users_changelog.txt" | |
# The function returns the user name from the public key file name. | |
# Example: public-keys/sshuser.pub => sshuser | |
get_user_name () { | |
echo "$1" | sed -e 's/.*\///g' | sed -e 's/\.pub//g' | |
} | |
# For each public key available in the S3 bucket | |
aws s3api list-objects --bucket $BUCKET_NAME --prefix public-keys/ --output text --query 'Contents[?Size>`0`].Key' | sed -e 'y/\t/\n/' > ~/keys_retrieved_from_s3 | |
while read line; do | |
USER_NAME="`get_user_name "$line"`" | |
# Make sure the user name is alphanumeric | |
if [[ "$USER_NAME" =~ ^[a-z][-a-z0-9]*$ ]]; then | |
# Create a user account if it does not already exist | |
cut -d: -f1 /etc/passwd | grep -qx $USER_NAME | |
if [ $? -eq 1 ]; then | |
/usr/sbin/adduser $USER_NAME && \ | |
mkdir -m 700 /home/$USER_NAME/.ssh && \ | |
chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh && \ | |
echo "$line" >> ~/keys_installed && \ | |
echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Creating user account for $USER_NAME ($line)" >> $LOG_FILE | |
fi | |
# Copy the public key from S3, if a user account was created | |
# from this key | |
if [ -f ~/keys_installed ]; then | |
grep -qx "$line" ~/keys_installed | |
if [ $? -eq 0 ]; then | |
aws s3 cp s3://$BUCKET_NAME/$line /home/$USER_NAME/.ssh/authorized_keys | |
chmod 600 /home/$USER_NAME/.ssh/authorized_keys | |
chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh/authorized_keys | |
fi | |
fi | |
fi | |
done < ~/keys_retrieved_from_s3 | |
# Remove user accounts whose public key was deleted from S3 | |
if [ -f ~/keys_installed ]; then | |
sort -uo ~/keys_installed ~/keys_installed | |
sort -uo ~/keys_retrieved_from_s3 ~/keys_retrieved_from_s3 | |
comm -13 ~/keys_retrieved_from_s3 ~/keys_installed | sed "s/\t//g" > ~/keys_to_remove | |
while read line; do | |
USER_NAME="`get_user_name "$line"`" | |
echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Removing user account for $USER_NAME ($line)" >> $LOG_FILE | |
/usr/sbin/userdel -r -f $USER_NAME | |
done < ~/keys_to_remove | |
comm -3 ~/keys_installed ~/keys_to_remove | sed "s/\t//g" > ~/tmp && mv ~/tmp ~/keys_installed | |
fi | |
EOF | |
chmod 700 /usr/bin/bastion/sync_users | |
###################################################### | |
# Set up cron jobs | |
###################################################### | |
# write cron task to file | |
cat > ~/bastion_cron << EOF | |
* * * * * /usr/bin/bastion/sync_s3 # every minute | |
* * * * * /usr/bin/bastion/sync_users # every minute | |
*/5 * * * * yum -y update --security # every 5 minutes | |
EOF | |
# start cron jobs | |
sudo crontab ~/bastion_cron | |
# delete created cron job file | |
rm ~/bastion_cron | |
###################################################### |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment