HiveBrain v1.2.0
Get Started
← Back to all entries
snippetMinor

How can I set let's encrypt certificates with Ansible?

Submitted by: @import:stackexchange-devops··
0
Viewed 0 times
canwithcertificateslethowencryptansibleset

Problem

I'm trying to get a let's encrypt certificate for my domain with Ansible.
I have been reading this tutorial which is a bit outdated and the Ansible documentation.

My playbook is a mix of what I have found in the tutorial mentioned and the documentation.
`---

  • name: "Create required directories in /etc/letsencrypt"


file:
path: "/etc/letsencrypt/{{ item }}"
state: directory
owner: root
group: root
mode: u=rwx,g=x,o=x
with_items:
- account
- certs
- csrs
- keys

  • name: Generate let's encrypt account key


openssl_privatekey:
path: "/etc/letsencrypt/account/account.key"

  • name: Generate let's encrypt private key with the default values (4096 bits, RSA)


openssl_privatekey:
path: "/etc/letsencrypt/keys/domain.me.key"

  • name: Generate an OpenSSL Certificate Signing Request


community.crypto.openssl_csr:
path: "/etc/letsencrypt/csrs/domain.me.csr"
privatekey_path: "/etc/letsencrypt/keys/domain.me.key"
common_name: www.domain.me

# Create challenge
  • name: Create a challenge for domain.me using an account key file.


acme_certificate:
acme_directory: "https://acme-v02.api.letsencrypt.org/directory"
acme_version: 2
account_key_src: "/etc/letsencrypt/account/account.key"
account_email: "email@mail.com"
terms_agreed: yes
challenge: "http-01"
src: "/etc/letsencrypt/csrs/domain.me.csr"
dest: "/etc/letsencrypt/certs/domain.me.crt"
fullchain_dest: "/etc/letsencrypt/certs/domain.me-fullchain.crt"
register: acme_challenge_domain_me

  • name: "Create .well-known/acme-challenge directory"


file:
path: "project/dir/path/.well-known/acme-challenge"
state: directory
owner: root
group: root
mode: u=rwx,g=rx,o=rx

  • name: "Implement http-01 challenge files"


copy:
content: "{{ acme_challenge_domain_me['challenge_data'][item]['http-01']['resource_value'] }}"
dest: "project/dir/path/{{ acme_challenge_domain_me['challenge_data'][item]['http-01']['resource'] }}

Solution

There were a couple mistakes in my tasks, but this one works:
---

# Create the directories required by letsencrypt to store keys and certificates.
  • name: "Create required directories in /etc/letsencrypt"


become: yes
file:
path: "/etc/letsencrypt/{{ item }}"
state: directory
owner: root
group: root
mode: u=rwx,g=x,o=x
with_items:
- account
- certs
- csrs
- keys

# https://docs.ansible.com/ansible/2.9/modules/acme_certificate_module.html#acme-certificate-module
  • name: Generate let's encrypt account key


become: yes
openssl_privatekey:
path: "{{ letsencrypt_account_key }}"

# https://docs.ansible.com/ansible/latest/collections/community/crypto/openssl_privatekey_module.html#openssl-privatekey-module
  • name: Generate let's encrypt private key with the default values (4096 bits, RSA)


become: yes
openssl_privatekey:
path: "{{letsencrypt_keys_dir}}/{{ domain_name }}.key"

# https://docs.ansible.com/ansible/latest/collections/community/crypto/openssl_csr_module.html#openssl-csr-module
  • name: Generate an OpenSSL Certificate Signing Request


become: yes
community.crypto.openssl_csr:
path: "{{letsencrypt_csrs_dir}}/{{ domain_name }}.csr"
privatekey_path: "{{letsencrypt_keys_dir}}/{{ domain_name }}.key"
common_name: "{{domain_name}}"

# Create letsencrypt challenge.
  • name: Create a challenge for {{domain_name}} using a account key file.


become: yes
community.crypto.acme_certificate:
acme_directory: "{{acme_directory}}"
acme_version: "{{acme_version}}"
account_email: "{{acme_email}}"
terms_agreed: yes
account_key_src: "{{letsencrypt_account_key}}"
csr: "{{letsencrypt_csrs_dir}}/{{domain_name}}.csr"
dest: "{{letsencrypt_certs_dir}}/{{domain_name}}.crt"
remaining_days: "{{remaining_days}}"
register: acme_challenge

# Create the directory to hold the validation token.
  • name: "Create .well-known/acme-challenge directory"


become: yes
file:
path: "{{project_path}}/.well-known/acme-challenge"
state: directory
owner: root
group: root
mode: u=rwx,g=rx,o=rx

# Copy the necessary files for the http-01 challenge.
  • name: "Implement http-01 challenge files"


become: yes
copy:
dest: "{{project_path}}/{{ acme_challenge['challenge_data'][item]['http-01']['resource'] }}"
content: "{{ acme_challenge['challenge_data'][item]['http-01']['resource_value'] }}"
with_items:
- "{{ domain_name }}"
when: acme_challenge is changed and domain_name|string in acme_challenge['challenge_data']

# Execute letsencrypt challenge.
  • name: Let the challenge be validated and retrieve the cert and intermediate certificate


become: yes
community.crypto.acme_certificate:
account_key_src: "{{letsencrypt_account_key}}"
csr: "{{letsencrypt_csrs_dir}}/{{domain_name}}.csr"
cert: "{{letsencrypt_certs_dir}}/{{domain_name}}.crt"
acme_directory: "{{acme_directory}}"
acme_version: "{{acme_version}}"
account_email: "{{acme_email}}"
challenge: "{{acme_challenge_type}}"
fullchain: "{{letsencrypt_certs_dir}}/{{domain_name}}-fullchain.crt"
chain: "{{letsencrypt_certs_dir}}/{{domain_name}}-intermediate.crt"
remaining_days: "{{remaining_days}}"
data: "{{ acme_challenge }}"
when: acme_challenge is changed


The corresponding Nginx config file is:

upstream {{project_name}} {
    server unix:{{socket_location}};
}

server {
    listen 80;
    location /.well-known/acme-challenge/ {
        root {{project_path}};
    }
    server_name {{domain_name}} www.{{domain_name}} {{web_server_ip}};
    return 301 https://{{domain_name}}$request_uri;
}

server {

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/certs/{{domain_name}}-fullchain.crt;
    ssl_certificate_key /etc/letsencrypt/keys/{{domain_name}}.key;

    charset utf-8;
    client_max_body_size 4M;

    # Serving static files directly from Nginx without passing through uwsgi 
    location /app/static/ {
        alias {{project_path}}/app/static/;
    }

    location /.well-known/acme-challenge/ {
        root {{project_path}};
    }

    location / {
        # kill cache
        add_header Last-Modified $date_gmt;
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
        if_modified_since off;
        expires off;
        etag off;
        uwsgi_pass {{project_name}};
        include {{project_path}}/uwsgi_params;
    }
}

Code Snippets

upstream {{project_name}} {
    server unix:{{socket_location}};
}

server {
    listen 80;
    location /.well-known/acme-challenge/ {
        root {{project_path}};
    }
    server_name {{domain_name}} www.{{domain_name}} {{web_server_ip}};
    return 301 https://{{domain_name}}$request_uri;
}

server {

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/certs/{{domain_name}}-fullchain.crt;
    ssl_certificate_key /etc/letsencrypt/keys/{{domain_name}}.key;

    charset utf-8;
    client_max_body_size 4M;

    # Serving static files directly from Nginx without passing through uwsgi 
    location /app/static/ {
        alias {{project_path}}/app/static/;
    }

    location /.well-known/acme-challenge/ {
        root {{project_path}};
    }

    location / {
        # kill cache
        add_header Last-Modified $date_gmt;
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
        if_modified_since off;
        expires off;
        etag off;
        uwsgi_pass {{project_name}};
        include {{project_path}}/uwsgi_params;
    }
}

Context

StackExchange DevOps Q#15891, answer score: 1

Revisions (0)

No revisions yet.