Using Pulumi and AWS Secrets Manager to Launch NGINX Plus Instances in AWS

Introduction

Some time ago we published an article on using CloudFormation Templates to create NGINX Plus instances. In this follow up article, we will be doing the same thing using Pulumi, an infrastructure as code platform that allows you to use familiar languages to write the code that builds your infrastructure. Within NGINX, we have used Pulumi to build a Modern Application Reference Architecture, which initially targets Kubernetes deployments. It might also be helpful to look at how you can use Pulumi to deploy NGINX Plus instances on AWS, into whatever VPC and subnet you specify.

A critical part of NGINX Plus install is to copy your NGINX Plus SSL Certificate and Key - required for access to the NGINX Plus private repository - into /etc/nginx/ssl/. There a few different ways to achieve this, but the AWS SecretsManager service provides a lot of audit, access control, and security capabilities.

In the previous CloudFormation example, the new EC2 Instance was configured to connect to AWS secrets manager and retrieve the secrets as part of the cloudinit post-install scripts. This is fine, but also requires creating an IAM role for the new server, which is an additional step, and increases the permissions of your instances. Because Pulumi integrates into common languages - Python in my example - we can be a little more flexible with how we do things, and we can retrieve the secrets and include them into the start up scrips (using the userdata field).

Prerequisites

You're going to need a functioning Pulumi installation that can communicate with AWS and a sample Python project. We recommend following the tutorial and checking you can perform the AWS equivalent of "Hello World' - creating an S3 Bucket. You can use this same project to create an NGINX install by editing the __main__.py file or create a new blank one.

You will also need your NGINX repo access certificate and key saved as secrets in AWS Secrets manager - in this case we are storing them with the names "nginxcert" and "nginxkey". You can get a free 30 day trial of NGINX Plus from NGINX.com

The Code Snippets

Let's take a look at the key parts of the Python code:

First import the pulumi modules

import pulumi
import pulumi_aws as aws

Now define some variables - in a production setting these would obviously NOT be hard coded, but read as parameters.

# Variables 

VPC = "<your VPC>"
AMI = "ami-0964546d3da97e3ab" # This is the ubuntu 20.04 LTS AMI for us-west-2
WebSecGroup = "<web server security group id>"
AdminSecGroup = "<admin SSH security group>"
subNet = "<subnet to deploy into>"
size= "t2.micro"
keyPair = "<SSH Key pair>"

Get the secret from AWS Secrets Manager:

# Get the secrets
cert = aws.secretsmanager.get_secret_version(secret_id="nginxcert")
key = aws.secretsmanager.get_secret_version(secret_id="nginxkey")

Next build the userdata for post install, this involves concatenating a number of strings together, along with the retrieved secrets, which need some minor text processing:

userdata = """#!/bin/bash -xe
sudo apt-get update -y # good practice to update existing packages
sudo mkdir /etc/ssl/nginx

# install the key and secret
echo \" """ 

# Add the certificate

userdata = userdata + cert.secret_string

userdata = userdata + """
\"| tr -d '"{}' | sudo tee /etc/ssl/nginx/nginx-repo.crt
echo \" """ 

# Add the key

userdata = userdata + key.secret_string

# Do the NGINX install 

userdata = userdata + """
\"| tr -d '"{}' | sudo tee /etc/ssl/nginx/nginx-repo.key
sudo wget https://cs.nginx.com/static/keys/nginx_signing.key && sudo apt-key add nginx_signing.key 
sudo wget https://cs.nginx.com/static/keys/app-protect-security-updates.key && sudo apt-key add app-protect-security-updates.key
sudo apt-get install -y apt-transport-https lsb-release ca-certificates
printf "deb https://pkgs.nginx.com/plus/ubuntu `lsb_release -cs` nginx-plus\n" | sudo tee /etc/apt/sources.list.d/nginx-plus.list
sudo wget -P /etc/apt/apt.conf.d https://cs.nginx.com/static/files/90pkgs-nginx
sudo apt-get update -y # good practice to update existing packages
sudo apt-get install nginx-plus -y # install web server   
sudo systemctl start nginx.service # start webserver 
""" 

Finally, we are going to create the instance:

server = aws.ec2.Instance('webserver-www',
  instance_type=size,
  vpc_security_group_ids=[WebSecGroup, AdminSecGroup], # reference security group from above
  ami=AMI, subnet_id=subNet, key_name=keyPair,
  user_data=userdata,associate_public_ip_address = True )

And then print out the private DNS name - although there are plenty more fields you may wish to export:

pulumi.export('ip',server.private_dns) 

To create the instance, we issue the "pulumi up" command:

% pulumi up                         
Previewing update (test)

View Live: https://app.pulumi.com/RuncibleSpoon/nginx/test/previews/2663305a-8544-4776-b2c3-4cd2abe0f723

   Type         Name      Plan    Info
 +  pulumi:pulumi:Stack nginx-test   create   6 warnings
 +  └─ aws:ec2:Instance webserver-www create   
 
Diagnostics:
 pulumi:pulumi:Stack (nginx-test):
  warning: rotation_enabled is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_lambda_arn is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_rules is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_enabled is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_lambda_arn is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_rules is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
 

Do you want to perform this update? yes
Updating (test)

View Live: https://app.pulumi.com/RuncibleSpoon/nginx/test/updates/30

   Type         Name      Status   Info
 +  pulumi:pulumi:Stack nginx-test   created   6 warnings
 +  └─ aws:ec2:Instance webserver-www created   
 
Diagnostics:
 pulumi:pulumi:Stack (nginx-test):
  warning: rotation_enabled is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_lambda_arn is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_rules is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_enabled is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_lambda_arn is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
  warning: rotation_rules is deprecated: Use the aws_secretsmanager_secret_rotation data source instead
 
Outputs:
    ip      : "ip-10-0-1-90.us-west-2.compute.internal"

Resources:
    + 2 created

Duration: 50s


This will build an NGINX Plus instance in the subnet of your choice. While there is more to be done to fully configure an NGINX instance this code provides a base on which to build more production-ready deployments.

Published Jan 10, 2022
Version 1.0
No CommentsBe the first to comment