Blog-Background

My Solution Architect Associate Resume Project Part 4 – Parameter Store and Configuring MariaDB

"What I cannot create, I do not understand" Richard Feynman

In the last two articles, we created two bash scripts, one to Launch an (RHEL-8) EC2 instance and another to install the LEMP stack, WordPress, and configure Nginx to serve WordPress.

Today we need to figure out a way to centrally store all DB credentials then pass them to create, configure, and secure MariaDB and WordPress inside EC2.

My initial instinct was to use Hashi’s Corp Vault, however, since being in the very early stages of this, I don’t want to introduce a lot of complexity. So after a bit of research, I landed on Systems Manager’s Parameter Store as it seems a perfect fit for such a task, there is also AWS Secrets Manager but it charges $0.4 per freaking secret, and since my secrets aren’t worth a damn I don’t wanna pay that.

Objective:

  • Create and store credentials in Parameter Store using AWS-CLI.
    • Create our keys
    • provision credentials in parameter store
  • Configure and secure MariaDB.
    • Installing AWS-CLI version 2 on RHEL
    • Retrieving the credentials from Parameter Store
    • Granting EC2 access rights to Parameter Store using IAM roles
    • Securing & Configuring MariaDB
    • Getting it all together

Creating and storing credentials in Parameter Store

Since we do everything in code, I will start this by writing a simple bash script that utilizes the AWS-CLI to create each parameter, since I will be checking this to source control I will generate all the values for these secrets randomly at run time and assign them to each parameter key we are about to define.

1.Creating our keys

After going through the docs for Parameter Store and checking how to organize parameters into hierarchies, which is great for organizing and managing secrets, I ended up deciding on using the following naming convention for my keys /enviroment(dev, staging, prod, etc..)/category/name

#!/bin/bash

website='hajr.io'
evn="dev"

#Keys 
db_name="/$env/db-server/name"
db_username="/$env/db-server/username"
db_password="/$env/db-server/password"

There is no right or wrong way here, as long as it’s concise enough and self-explanatory for anyone who will read it.

2.Provision credentials in parameter store using AWS-CLI

Since we need to generate the values for these keys, we will start by writing an alias for the command that we will use for this which is "opensll", and just expand the aliases within the shell that will be created by the script using "shopt" so that it works.

shopt -s expand_aliases
alias rand=$(openssl rand -base64 16)

Now that we have our random 16 character generator alias and the keys, let’s create the parameters.

aws ssm put-parameter \
    --name $db_name \
    --value `rand` \
    --type String \
    --tags "Key=ENV,Value=$env" \
    --region $region

aws ssm put-parameter \
    --name $db_username \
    --value `rand` \
    --type String \
    --tags "Key=ENV,Value=$env" \
    --region $region

aws ssm put-parameter \
    --name $db_password \
    --value `rand` \
    --type SecureString \
    --tags "Key=ENV,Value=$env" \
    --region $region

So we are just creating the parameters with the keys we decided on earlier and then generating a random value for each one of them.

So here is the full script.

#!/bin/bash

evn="dev"
region="eu-west-3"

shopt -s expand_aliases
alias rand=$(openssl rand -base64 16)


#Keys 
db_host="/$env/db-server/host"
db_name="/$env/db-server/name"
db_username="/$env/db-server/username"
db_password="/$env/db-server/password"


aws ssm put-parameter \
    --name $db_name \
    --value $(rand) \
    --type String \
    --tags "Key=ENV,Value=$env" \
    --region $region

aws ssm put-parameter \
    --name $db_username \
    --value $(rand) \
    --type String \
    --tags "Key=ENV,Value=$env" \
    --region $region

aws ssm put-parameter \
    --name $db_password \
    --value $(rand) \
    --type SecureString \
    --tags "Key=ENV,Value=$env" \
    --region $region

Securing & Configuring MariaDB

Since we now have our credentials in Parameter Store the first step will be to fetch them as we will need them to configure MariaDB, however unlike Amazon Linux AMIs, RedHat ones don’t come with AWS-CLI so we first need to install AWS-CLI then use it to fetch the secrets from parameter store

1. Installing AWS-CLI version 2 on RHEL

#installing AWS CLI version 2 
function installAwsCli {
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    unzip awscliv2.zip
    bash ./aws/install
    rm -rf ./aws awscliv2.zip
}

As you can see above a very simple function just downloading the package then running its install script and cleaning after it’s done.

2. Retrieving the credentials from Parameter Store

Now that the CLI installed we will use the following command to retrieve our secrets

aws ssm get-parameter --name "param-name" --query "Parameter.Value" --output text --region $region

Utilizing the above command we will create a function to get the 3 credentials(name, username, password) we need and assign them to global variables within the script(I”m sorry for this) so we can use them later on.

#database credentials
password=''
username=''
name=''


#installing AWS CLI version 2 
function installAwsCli {
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    unzip awscliv2.zip
    bash ./aws/install
    rm -rf ./aws awscliv2.zip
}

#Get Database credentials
function getAndSetCredentials {
    path="/dev/db-server"
    password=$(aws ssm get-parameter --name "$path/password" --with-decryption --query "Parameter.Value" --output text --region $region)

    username=$(aws ssm get-parameter --name "$path/username" --query "Parameter.Value" --output text --region $region)

    name=$(aws ssm get-parameter --name "$path/name" --query "Parameter.Value" --output text --region $region)
}

Even though I despise global state and I don’t code like this, however, am new to bash, and writing good code is not a priority at all right now as am sure a lot of this code will be replaced once I learn and start implementing stuff using Terraform later in this series, so that would work for now.

3. Granting EC2 access rights to Parameter Store using IAM roles

In order for the above code to work, our instance needs to have access rights to fetch parameters from Parameter Store, instead of the headache of passing credentials we will use an IAM Role and assign it to our instance in order to have the access rights we need.

In order to make this happen we need two policies, one for the ec2 instance so it can assume the role and another one for the parameter store APIs we will allow the ec2 to have access to.

Here are the following policies I will name them trust-policy.json and parameter-store-access.json respectively

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
 }
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "ssm:GetParametersByPath",
                "ssm:GetParameters",
                "ssm:GetParameter"
            ],
            "Resource": "*"
        }
    ]
}

Now that we have our policies let’s use the aws-cli to create this role

aws iam create-role --role-name ps-access --assume-role-policy-document file://policies/trust-policy.json

aws iam put-role-policy --role-name ps-access --policy-name param-store-access-policy --policy-document file://policies/parameter-store-access.json

So the first command create the role and attach the trust policy that allows EC2 to assume this role.

The second command attach the parameter store access policy to the role and we are good to go.

The last thing that needs to be done is to attach that role to the EC2 instance that will be created, if you recall we created a script in the second blog to automate the creation of EC2, all that we need to do is to add the above 2 commands to create the role in it, then pass that role in the run-instances command using -iam-instance-profile like the following

aws ec2 run-instances --image-id $ami_id --iam-instance-profile Name="ps-access" --count 1 --instance-type t2.micro --key-name $key_pair_name --security-group-ids $sg_id --user-data file://bootstrap.sh --region $region

Now that our EC2 bootstrap script can successfully retrieve parameters from parameter store we can start securing and configuring MariaDB

4. Securing & Configuring MariaDB

So the common way that every blog and video preach that I found so far to secure MariaDB for production is to run the famous mysql_secure_installation, I’m sure there is way more to securing a SQL DB than that but I will stick to this for now.

After looking at how can I automate that script as its a pretty interactive one, I found that the best way that I like is to extract the SQL commands from the script itself which quite straightforward actually when you look at the script itself on Github, and just to be sure am doing the right thing I looked for a blog post about this and found this one which was very helpful and I ended up with this

mysql --user=root <<-EOF
    UPDATE mysql.user SET Password=PASSWORD('$password') WHERE User='root';
    DELETE FROM mysql.user WHERE User='';
    DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
    DROP DATABASE IF EXISTS test;
    DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
    CREATE DATABASE $name;
    CREATE USER '$username'@'localhost' identified by '$password';
    GRANT ALL ON $name.* TO '$username'@'localhost' identified by '$password';
    FLUSH PRIVILEGES;
EOF

This is just doing the steps that the scripts do under the hood which is basically setting the root password, removing (Anonymus users, remote login to the DB, and the test DB), Then creating the DB and a user in it for our WordPress application to use.

5. Getting it all together

Finally, Adding the above two sections for MariaDB to the EC2 Bootstrap script we developed in the last article and we have this.

#!/bin/bash

website='wordpress'
region="eu-west-3"
#database credentials
password=''
username=''
name=''


#Update and install LEMP stack packages and dependencies for WordPress
function installPackages {
    yum update -y
    yum module install php nginx mariadb -y
    yum install php-mysqlnd php-fpm php-json unzip -y
}

#installing AWS CLI version 2 
function installAwsCli {
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    unzip awscliv2.zip
    bash ./aws/install
    rm -rf ./aws awscliv2.zip
}

#Installing and configuring WordPress
function installWordPress {
    mkdir /var/www/$website
    cd /var/www/$website
    curl https://wordpress.org/latest.tar.gz -o ./latest.tar.gz
    tar -xzvf ./latest.tar.gz
    cp -r wordpress/* .
    rm -rf latest.tar.gz wordpress 
    find . -type d -exec chmod 755 {} \;
    find . -type f -exec chmod 644 {} \;
    chown -R nginx:nginx wp-content
}

#downloanding and overwriting the  Nginx configuration files 
function configuringNginx {
    github_raw_url='https://raw.githubusercontent.com/MohamedHajr/AWS-Wordrpess-Portflio-Automation/master'
    curl "$github_raw_url/hajr.io.conf" -o /etc/nginx/conf.d/hajr.io.conf
    curl "$github_raw_url/nginx.conf" > /etc/nginx/nginx.conf
}

#Get Database credentials
function getAndSetCredentials {
    path="/dev/db-server"
    password=$(aws ssm get-parameter --name "$path/password" --with-decryption --query "Parameter.Value" --output text --region $region)
    username=$(aws ssm get-parameter --name "$path/username" --query "Parameter.Value" --output text --region $region)
    name=$(aws ssm get-parameter --name "$path/name" --query "Parameter.Value" --output text --region $region)
}

#Securing MariaDB by automaing what mysql_secure_installtion does behind the sceanes
#And Creating a new database and user for WordPress
function configuringMariaDB {
mysql --user=root <<-EOF
    UPDATE mysql.user SET Password=PASSWORD('$password') WHERE User='root';
    DELETE FROM mysql.user WHERE User='';
    DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
    DROP DATABASE IF EXISTS test;
    DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
    CREATE DATABASE $name;
    CREATE USER '$username'@'localhost' identified by '$password';
    GRANT ALL ON $name.* TO '$username'@'localhost' identified by '$password';
    FLUSH PRIVILEGES;
EOF
}

#Running Everything
installPackages
installAwsCli
installWordPress
configuringNginx

#Spining everything
systemctl enable --now nginx php-fpm mariadb

#Configure MariaDB
getAndSetCredentials
configuringMariaDB

So in this article, we learned how to create and store credentials in Parameter Store, then we used those credentials, to secure and configure MariaDB for WordPress.

In the next article we will be fishing all our application level automation by automating the configuration of WordPress then we will finally start the most exciting part which is using terraform to start provisioning our infrastructure as code.

Thanks for reading thus far, let me know if you have any questions or suggestions and looking forward to seeing you in the next part, cheers ^^

Share this post

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on print
Share on email
Recent Comments

    Leave a Reply

    Your email address will not be published.