Over a million developers have joined DZone.

Automating LetsEncrypt Certificates With Ansible for AWS Instances

DZone's Guide to

Automating LetsEncrypt Certificates With Ansible for AWS Instances

Learn how to more conveniently make your AWS instance safer by automatically generating LetsEncrypt certificates.

· Cloud Zone ·
Free Resource

Learn how to migrate and modernize stateless applications and run them in a Kubernetes cluster.

LetsEncrypt is a free certificate provider and myriads of tools and technologies available to automate its certificate generation. Here, we use Ansible and and letsencrypt/dehydrated client to generate certificates for an AWS instance configured as a proxy server using HAProxy.

Create Ansible Inventory for Proxy Server

Lets start with retrieving AWS instance that has HAProxy installed already and create a dynamic inventory. HAProxy instance is spun up with a tag "Role: proxy-server" and we can use the same tag to filter retrieve the proxy instance.

All the tasks described here are assumed to have appropriate aws_access_key, aws_secret_key and region setup properly and omitted here to avoid repetition.                         

- ec2_remote_facts:
      instance-state-name: running
      "tag:role": proxy-server
  register: proxy_instances

Now, populate the proxy server IP address in the dynamic inventory. 

- name: Add proxy to dynamic host group
    name: "{{ item.tags.Name }}"
    groupname: groupName
    ansible_ssh_host: "{{ item.private_ip_address }}"
    ansible_user: userName
  with_items: "{{ proxy_instances.instances }}"
  tags: inventory

Install LetsEncrypt/Dehydrated Client

LetsEncrypt requires us to prove that we own the domain for which we request a certificate. Say we own a domain, "myexample.org," in the public zone, "myexample.org," in AWS Route53. Now we have to prove to LetsEncrypt that we own the domain "myexample.org." Fortunately, we can do that via HTTP-based or dns-01 challenges. We will be using a dns-01-based challenge, as it's pretty straightforward with AWS Route53, and the dehydrated client can automate that.

Let's install the dehydrated client from GitHub:

- name: Clone dehydrated repo into configured directory.
  delegate_to: "{{ item.tags.Name }}"
  become: yes
    repo: "{{ dehydrated_repo }}"
    dest: "{{ dehydrated_dir }}"
    version: "{{ dehydrated_version }}"
    update: "{{ dehydrated_keep_updated }}"
  tags: dehydrated
  with_items: "{{ proxy_instances.instances }}"

We will make it executable:

- name: Ensure dehydrated is executable.
  delegate_to: "{{ item.tags.Name }}"
  become: yes
    path: "{{ dehydrated_dir }}/dehydrated"
    mode: 0755
  tags: dehydrated
  with_items: "{{ proxy_instances.instances }}"

And we'll create a directory for the dehydrated supporting files:

- name: create directory for dehydrated files.
  delegate_to: "{{ item.tags.Name }}"
  become: yes
  file: path=/etc/dehydrated state=directory mode=0755
  with_items: "{{ proxy_instances.instances }}"

Configure letsencrypt/dehydrated client:

we need domains.txt file which contains entry for the domain and subdomain for which we are requesting certificate. Please note that LetsEncrypt CA does not support wild card certificate at the moment of this writing. So we have to include all the subdomain names in domains.txt so that every subdomain name can be included in the same certificate itself.

In our case we are generating certificate for main domain "example.org" and a subdomain "app1.example.org". So, our domains.txt looks as follows:

example.org app1.example.org

Next,  download the dns-01 hook from github at https://gist.github.com/joshgarnett/02920846fea35f738d3370fd991bb0e0 to automate the dns challenge for our domain. This hook is a ruby based so pre-install the proxy instance with ruby. 

Finally, Create a dehydrated config file to specify required configuration values for dehydrated client to work as we wanted.

Config file contains url to LetsEncrypt certificate API, challenge type, ruby hook for the challenge and certificate admin email id:


Now copy all the supporting files in target instance

- name: copy dehydrated files
  delegate_to: "{{ item[1].tags.Name }}"
  become: yes
  copy: src={{ item[0] }} dest=/etc/dehydrated owner=root mode=0775
    - ['files/config', 'files/domains.txt', 'files/lets-encrypt-route53.rb' ]
    - "{{ proxy_instances.instances }}"

Generate Certificates and Apply

If everything went well so far, we'll get started generating a certificate:

- name: Generate certificate
  delegate_to: "{{ item.tags.Name }}"
  become: yes
      AWS_REGION: "{{ aws_region }}"
      AWS_ACCESS_KEY_ID: "{{ aws_access_key }}"
      AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}"
  shell: "/opt/dehydrated/dehydrated -c"
  register: certificates_resp
  with_items: "{{ proxy_instances.instances }}"

We supply AWS credentials via environment variables for our DNS hook to work. So, replace these variables with your AWS credentials.

It should generate the certificate and private key in the target location. Now we have to combine the fullchain certificate and private key file into a new file as "example.org.pem" and assign it to the HAProxy.

- name: combine certificate and priv key into main cert
  delegate_to: "{{ item.tags.Name }}"
  become: yes
    cmd: cat fullchain.pem privkey.pem > example.org.pem
    chdir: /etc/dehydrated/certs/example.org
  tags: cert
  with_items: "{{ proxy_instances.instances }}"

 We can assign the certificate to HAProxy as follows:

frontend https_443_frontend
  bind *:443 ssl crt /etc/dehydrated/certs/example.org/example.org.pem

Hit your application and observe that your app is up and running with HTTPS.

Join us in exploring application and infrastructure changes required for running scalable, observable, and portable apps on Kubernetes.

ssl certificates ,ansible ,aws ,cloud

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}