snippetMinor
How can I set let's encrypt certificates with Ansible?
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.
`---
file:
path: "/etc/letsencrypt/{{ item }}"
state: directory
owner: root
group: root
mode: u=rwx,g=x,o=x
with_items:
- account
- certs
- csrs
- keys
openssl_privatekey:
path: "/etc/letsencrypt/account/account.key"
openssl_privatekey:
path: "/etc/letsencrypt/keys/domain.me.key"
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
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
file:
path: "project/dir/path/.well-known/acme-challenge"
state: directory
owner: root
group: root
mode: u=rwx,g=rx,o=rx
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'] }}
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:
The corresponding Nginx config file is:
---
# 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.