1. Answers
  2. Automating TLS Certificate Issuance With Let's Encrypt

Automating TLS Certificate Issuance With Let's Encrypt

Introduction

In this guide, we will automate the issuance of TLS certificates using Let’s Encrypt with Pulumi. Let’s Encrypt is a free, automated, and open certificate authority that provides TLS certificates to secure websites. We will use Pulumi to provision and manage the necessary resources in AWS, including an S3 bucket for storing the certificates and an EC2 instance to run Certbot, the Let’s Encrypt client.

Step-by-Step Explanation

Step 1: Set Up Pulumi Project

  1. Create a new Pulumi project.
  2. Configure AWS credentials and region.
  3. Install the necessary Pulumi packages for AWS and Let’s Encrypt.

Step 2: Create an S3 Bucket

  1. Define an S3 bucket resource to store the TLS certificates.
  2. Configure the bucket policy to allow public read access to the .well-known directory for the ACME challenge.

Step 3: Launch an EC2 Instance

  1. Define an EC2 instance resource to run Certbot.
  2. Configure the instance with a user data script to install Certbot and run it to obtain the certificates.
  3. Attach an IAM role to the instance to allow it to write to the S3 bucket.

Step 4: Automate Certificate Renewal

  1. Set up a cron job on the EC2 instance to renew the certificates periodically.
  2. Configure the instance to upload the renewed certificates to the S3 bucket.

Conclusion

By following these steps, you can automate the issuance and renewal of TLS certificates using Let’s Encrypt with Pulumi. This ensures that your website remains secure with up-to-date certificates without manual intervention.

Full Code Example

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create an S3 bucket to store the TLS certificates
const bucket = new aws.s3.Bucket("certificatesBucket", {
    acl: "private",
});

// Create a bucket policy to allow public read access to the .well-known directory
const bucketPolicy = new aws.s3.BucketPolicy("bucketPolicy", {
    bucket: bucket.id,
    policy: bucket.id.apply(id => JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Effect: "Allow",
                Principal: "*",
                Action: "s3:GetObject",
                Resource: \`arn:aws:s3:::\${id}/.well-known/*\`,
            },
        ],
    })),
});

// Create an IAM role for the EC2 instance
const role = new aws.iam.Role("certbotRole", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Action: "sts:AssumeRole",
                Principal: {
                    Service: "ec2.amazonaws.com",
                },
                Effect: "Allow",
                Sid: "",
            },
        ],
    }),
});

// Attach the S3 write policy to the role
const policy = new aws.iam.Policy("certbotPolicy", {
    policy: bucket.arn.apply(arn => JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Effect: "Allow",
                Action: [
                    "s3:PutObject",
                ],
                Resource: \`\${arn}/*\`,
            },
        ],
    })),
});

const rolePolicyAttachment = new aws.iam.RolePolicyAttachment("rolePolicyAttachment", {
    role: role.name,
    policyArn: policy.arn,
});

// User data script to install Certbot and obtain certificates
const userData = \`#!/bin/bash
sudo yum update -y
sudo yum install -y certbot
certbot certonly --webroot -w /var/www/html -d yourdomain.com --non-interactive --agree-tos --email your-email@example.com
aws s3 cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem s3://\${bucket.bucket}/fullchain.pem
aws s3 cp /etc/letsencrypt/live/yourdomain.com/privkey.pem s3://\${bucket.bucket}/privkey.pem
\`;

// Create an EC2 instance to run Certbot
const instance = new aws.ec2.Instance("certbotInstance", {
    instanceType: "t2.micro",
    ami: "ami-0c55b159cbfafe1f0", // Amazon Linux 2 AMI
    userData: userData,
    iamInstanceProfile: role.name,
});

// Export the bucket name and instance ID
export const bucketName = bucket.bucket;
export const instanceId = instance.id;

Deploy this code

Want to deploy this code? Sign up for a free Pulumi account to deploy in a few clicks.

Sign up

New to Pulumi?

Want to deploy this code? Sign up with Pulumi to deploy in a few clicks.

Sign up