Let's Encrypt SSL certificate management automation using DNS-01 challenge and AWS

The post describes the steps to automate the management of SSL certificates from Let's Encrypt CA using DNS-01 challenge ΠΈ AWS.

acme-dns-route53 is a tool that will allow us to implement this feature. It can work with SSL certificates from Let's Encrypt, store them in Amazon Certificate Manager, use the Route53 API to implement the DNS-01 challenge, and, finally, push notifications to SNS. IN acme-dns-route53 there is also built-in functionality for use inside AWS Lambda, which is what we need.

This article is divided into 4 sections:

  • creating a zip file;
  • creating an IAM role;
  • creating a lambda function that runs acme-dns-route53;
  • creating a CloudWatch timer that triggers a function 2 times a day;

Note: must be installed before starting Golang 1.9+ ΠΈ AWS CLI

Create a zip file

acme-dns-route53 is written in GoLang and supports at least version 1.9.

We need to create a zip file with the binary acme-dns-route53 inside. For this you need to install acme-dns-route53 from the GitHub repository using the command go install:

$ env GOOS=linux GOARCH=amd64 go install github.com/begmaroman/acme-dns-route53

The binary is installed in $GOPATH/bin directory. Please note that during installation we specified two environment variables: GOOS=linux ΠΈ GOARCH=amd64. They make it clear to the Go compiler that it needs to create a binary suitable for Linux OS and amd64 architecture - this is what runs in AWS.
AWS expects our program to be deployed in a zip file, so let's create acme-dns-route53.zip archive that will contain the newly installed binary:

$ zip -j ~/acme-dns-route53.zip $GOPATH/bin/acme-dns-route53

Note: The binary must be in the root of the zip archive. For this we use -j flag.

Now our zip-nickname is ready for deployment, it remains only to create a role with the necessary rights.

Creating an IAM role

We need to set up the IAM role with the permissions our lambda needs when it executes.
Let's call this policy lambda-acme-dns-route53-executor and immediately give her a basic role AWSLambdaBasicExecutionRole. This will allow our lambda to run and write logs to the AWS CloudWatch service.
First, we create a JSON file that describes our rights. This will essentially allow lambda services to use the role lambda-acme-dns-route53-executor:

$ touch ~/lambda-acme-dns-route53-executor-policy.json

The content of our file is as follows:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup"
            ],
            "Resource": "arn:aws:logs:<AWS_REGION>:<AWS_ACCOUNT_ID>:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogStream"
            ],
            "Resource": "arn:aws:logs:<AWS_REGION>:<AWS_ACCOUNT_ID>:log-group:/aws/lambda/acme-dns-route53:*"
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "cloudwatch:PutMetricData",
                "acm:ImportCertificate",
                "acm:ListCertificates"
            ],
            "Resource": "*"
        },
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "sns:Publish",
                "route53:GetChange",
                "route53:ChangeResourceRecordSets",
                "acm:ImportCertificate",
                "acm:DescribeCertificate"
            ],
            "Resource": [
                "arn:aws:sns:${var.region}:<AWS_ACCOUNT_ID>:<TOPIC_NAME>",
                "arn:aws:route53:::hostedzone/*",
                "arn:aws:route53:::change/*",
                "arn:aws:acm:<AWS_REGION>:<AWS_ACCOUNT_ID>:certificate/*"
            ]
        }
    ]
}

Now let's run the command aws iam create-role to create a role:

$ aws iam create-role --role-name lambda-acme-dns-route53-executor 
 --assume-role-policy-document ~/lambda-acme-dns-route53-executor-policy.json

Note: remember the policy ARN (Amazon Resource Name) - we will need it in the next steps.

Role lambda-acme-dns-route53-executor created, now we need to specify permissions for it. The easiest way to do this is to use the command aws iam attach-role-policy, passing policy ARN AWSLambdaBasicExecutionRole in the following way:

$ aws iam attach-role-policy --role-name lambda-acme-dns-route53-executor 
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Note: a list with the rest of the policies can be found here.

Creating a lambda function that runs acme-dns-route53

Hooray! Now we can deploy our function on AWS using the command aws lambda create-function. The lambda must be configured using the following environment variables:

  • AWS_LAMBDA - makes it clear acme-dns-route53 that the execution happens inside AWS Lambda.
  • DOMAINS β€” list of domains separated by commas.
  • LETSENCRYPT_EMAIL - contains Let's Encrypt Email.
  • NOTIFICATION_TOPIC β€” SNS Notification Topic name (optional).
  • STAGING - with a value 1 staging environment is used.
  • 1024 MB - memory limit, can be changed.
  • 900 secs (15 min) - timeout.
  • acme-dns-route53 - the name of our binary, which is in the archive.
  • fileb://~/acme-dns-route53.zip is the path to the archive we created.

Now let's deploy:

$ aws lambda create-function 
 --function-name acme-dns-route53 
 --runtime go1.x 
 --role arn:aws:iam::<AWS_ACCOUNT_ID>:role/lambda-acme-dns-route53-executor 
 --environment Variables="{AWS_LAMBDA=1,DOMAINS="example1.com,example2.com",[email protected],STAGING=0,NOTIFICATION_TOPIC=acme-dns-route53-obtained}" 
 --memory-size 1024 
 --timeout 900 
 --handler acme-dns-route53 
 --zip-file fileb://~/acme-dns-route53.zip

 {
     "FunctionName": "acme-dns-route53", 
     "LastModified": "2019-05-03T19:07:09.325+0000", 
     "RevisionId": "e3fadec9-2180-4bff-bb9a-999b1b71a558", 
     "MemorySize": 1024, 
     "Environment": {
         "Variables": {
            "DOMAINS": "example1.com,example2.com", 
            "STAGING": "1", 
            "LETSENCRYPT_EMAIL": "[email protected]", 
            "NOTIFICATION_TOPIC": "acme-dns-route53-obtained", 
            "AWS_LAMBDA": "1"
         }
     }, 
     "Version": "$LATEST", 
     "Role": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/lambda-acme-dns-route53-executor", 
     "Timeout": 900, 
     "Runtime": "go1.x", 
     "TracingConfig": {
         "Mode": "PassThrough"
     }, 
     "CodeSha256": "+2KgE5mh5LGaOsni36pdmPP9O35wgZ6TbddspyaIXXw=", 
     "Description": "", 
     "CodeSize": 8456317,
"FunctionArn": "arn:aws:lambda:us-east-1:<AWS_ACCOUNT_ID>:function:acme-dns-route53", 
     "Handler": "acme-dns-route53"
 }

Creating a CloudWatch timer that triggers a function 2 times a day

The last step is to set up a cron that calls our function twice a day:

  • create CloudWatch rule with value schedule_expression.
  • create a rule target (what should be done) by specifying the ARN of the lambda function.
  • give permission to the rule to call the lambda function.

I've attached my Terraform config below, but it's actually very easy to do using the AWS Console or AWS CLI.

# Cloudwatch event rule that runs acme-dns-route53 lambda every 12 hours
resource "aws_cloudwatch_event_rule" "acme_dns_route53_sheduler" {
  name                = "acme-dns-route53-issuer-scheduler"
  schedule_expression = "cron(0 */12 * * ? *)"
}

# Specify the lambda function to run
resource "aws_cloudwatch_event_target" "acme_dns_route53_sheduler_target" {
  rule = "${aws_cloudwatch_event_rule.acme_dns_route53_sheduler.name}"
  arn  = "${aws_lambda_function.acme_dns_route53.arn}"
}

# Give CloudWatch permission to invoke the function
resource "aws_lambda_permission" "permission" {
  action        = "lambda:InvokeFunction"
  function_name = "${aws_lambda_function.acme_dns_route53.function_name}"
  principal     = "events.amazonaws.com"
  source_arn    = "${aws_cloudwatch_event_rule.acme_dns_route53_sheduler.arn}"
}

Now you are configured to automatically create and update SSL certificates

Source: habr.com

Add a comment