diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7b3dc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vagrant/ +/certs/ diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..3e9258c --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,35 @@ +# This guide is optimized for Vagrant 1.7 and above. +# Although versions 1.6.x should behave very similarly, it is recommended +# to upgrade instead of disabling the requirement below. +Vagrant.require_version ">= 1.7.0" + +Vagrant.configure(2) do |config| + + config.vm.box = "debian/buster64" + config.vm.synced_folder ".", "/vagrant", disabled: true + # Disable the new default behavior introduced in Vagrant 1.7, to + # ensure that all Vagrant machines will use the same SSH key pair. + # See https://github.com/mitchellh/vagrant/issues/5005 + config.ssh.insert_key = false + + config.vm.provider :libvirt do |lv| + lv.cpus = 1 + lv.memory = 512 + end + + config.vm.define "srv1" do |m| + m.vm.hostname = "srv1" + m.vm.network :private_network, ip: "192.168.123.30", libvirt__dhcp_enabled: false + end + config.vm.define "srv2" do |m| + m.vm.hostname = "srv2" + m.vm.network :private_network, ip: "192.168.123.31", libvirt__dhcp_enabled: false + end + + config.vm.provision "ansible" do |ansible| + #ansible.become = true + ansible.verbose = "v" + ansible.playbook = "playbook.yml" + ansible.inventory_path = "inventory.yml" + end +end diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..99ab541 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +roles_path = /root/.ansible/roles/:../ diff --git a/certs/.gitkeep b/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/inventory.yml b/inventory.yml new file mode 100644 index 0000000..b897cb4 --- /dev/null +++ b/inventory.yml @@ -0,0 +1,18 @@ +--- +all: + hosts: + srv1: + ansible_host: 192.168.123.30 + srv2: + ansible_host: 192.168.123.31 + vars: + cert_dir: ./certs + generate_ca_cert: true + generate_client_cert: true + generate_server_cert: true + tls_ca_email: me@example.org + tls_ca_country: EU + tls_ca_state: Italy + tls_ca_locality: Rome + tls_ca_organization: Example Inc. + tls_ca_organizationalunit: SysAdmins diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..5e1eecb --- /dev/null +++ b/playbook.yml @@ -0,0 +1,5 @@ +--- +- name: Run role + hosts: all + roles: + - role: generate-tls-certs diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..e2b522d --- /dev/null +++ b/requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - community.crypto diff --git a/tasks/generate-ca-cert.yaml b/tasks/generate-ca-cert.yaml index 55819df..75d6d74 100644 --- a/tasks/generate-ca-cert.yaml +++ b/tasks/generate-ca-cert.yaml @@ -1,20 +1,65 @@ --- - - name: Generate CA private key - local_action: - module: openssl_privatekey - path: "{{cert_dir}}/{{tls_ca_key}}" - size: "{{tls_ca_key_size}}" - run_once: true +- name: Check if the CA private key exists + delegate_to: localhost + ansible.builtin.stat: + path: "{{ cert_dir }}/{{ tls_ca_key }}" + register: ca_key - - name: Generate self-signed cert for CA - local_action: - module: | - shell if [ ! -e {{cert_dir}}/{{tls_ca_cert}} ] - then - openssl req -x509 -new -days {{tls_ca_valid_days}} -sha256 -nodes -key {{cert_dir}}/{{tls_ca_key}} -out {{cert_dir}}/{{tls_ca_cert}} \ - -subj "{% if tls_ca_country is defined%}/C={{tls_ca_country}}{% endif %}{% if tls_ca_state is defined%}/ST={{tls_ca_state}}{% endif %}{% if tls_ca_locality is defined %}/L={{tls_ca_locality}}{% endif %}{% if tls_ca_organization is defined %}/O={{tls_ca_organization}}{% endif %}{% if tls_ca_organizationalunit is defined %}/OU={{tls_ca_organizationalunit}}{% endif %}/CN={{tls_ca_commonname}}{% if tls_ca_email is defined %}/emailAddress={{tls_ca_email}}{% endif %}" - fi - args: - executable: /bin/bash - ignore_errors: true - run_once: true +- name: Generate CA private key + delegate_to: localhost + community.crypto.openssl_privatekey: + path: "{{ cert_dir }}/{{ tls_ca_key }}" + size: "{{ tls_ca_key_size }}" + run_once: true + when: not ca_key.stat.exists + +- name: Check if the CA CSR exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ tls_ca_csr }}" + register: ca_csr + +- name: Create CSR for CA + delegate_to: localhost + community.crypto.openssl_csr: + path: "{{ cert_dir }}/{{ tls_ca_csr }}" + privatekey_path: "{{ cert_dir }}/{{ tls_ca_key }}" + basic_constraints: + - "CA:TRUE" + common_name: "{{ tls_ca_commonname|default('') }}" + country_name: "{{ tls_ca_country|default('') }}" + state_or_province_name: "{{ tls_ca_state|default('') }}" + locality_name: "{{ tls_ca_locality|default('') }}" + organization_name: "{{ tls_ca_organization|default('') }}" + organizational_unit_name: "{{ tls_ca_organizationalunit|default('') }}" + email_address: "{{ tls_ca_email }}" + use_common_name_for_san: no + when: not ca_csr.stat.exists + +- name: Check if the CA cert exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ tls_ca_cert }}" + register: ca_cert + +- name: Create and sign server cert for CA + delegate_to: localhost + community.crypto.x509_certificate: + path: "{{ cert_dir }}/{{ tls_ca_cert }}" + privatekey_path: "{{ cert_dir }}/{{ tls_ca_key }}" + csr_path: "{{ cert_dir }}/{{ tls_ca_csr }}" + selfsigned_not_after: "+{{ tls_ca_valid_days }}d" + provider: selfsigned + when: not ca_cert.stat.exists + register: ca_cert_file + +- name: Copy the CA certificate to the remote machine + copy: + src: "{{ cert_dir }}/{{ tls_ca_cert }}" + dest: /etc/ssl/certs/ + mode: 0644 + owner: root + group: root + force: yes + backup: yes + when: ca_cert_file.changed diff --git a/tasks/generate-client-cert.yaml b/tasks/generate-client-cert.yaml index 5eb10cc..1dd6e5e 100644 --- a/tasks/generate-client-cert.yaml +++ b/tasks/generate-client-cert.yaml @@ -1,46 +1,87 @@ --- +- name: Ensure the custom directories to host certificates are present + become: yes + file: + state: directory + recurse: yes + path: "/etc/ssl/{{ item.path }}" + mode: "{{ item.mode }}" + owner: root + group: root + loop: + - {path: local/certs, mode: "0755"} + - {path: local/private, mode: "0700"} - - name: Generate client private key - local_action: - module: openssl_privatekey - path: "{{cert_dir}}/{{tls_client_key}}" - size: "{{tls_client_key_size}}" - run_once: true - when: generate_client_cert +- name: Check if the client private key exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ tls_client_key }}" + register: client_key - - name: Generate CSR and key for client cert - local_action: - module: | - shell if [ ! -e {{cert_dir}}/{{tls_client_csr}} ] - then - openssl req -newkey rsa:{{tls_client_key_size}} -nodes -subj "/CN={{tls_client_commonname}}" \ - -keyout "{{cert_dir}}/{{tls_client_key}}" -out "{{cert_dir}}/{{tls_client_csr}}" - fi - args: - executable: /bin/bash - ignore_errors: true - run_once: true - when: generate_client_cert +- name: Generate client private key + delegate_to: localhost + community.crypto.openssl_privatekey: + path: "{{ cert_dir }}/{{ tls_client_key }}" + size: "{{ tls_client_key_size}}" + when: + - not client_key.stat.exists + - generate_client_cert + register: client_key_file - - name: Add required extension for client authentication - local_action: - module: > - shell echo extendedKeyUsage = clientAuth >> {{cert_dir}}/{{tls_client_extfile}} - ignore_errors: true - run_once: true - when: generate_client_cert +- name: Copy the key on the server + become: yes + copy: + src: "{{ cert_dir }}/{{ tls_client_key}}" + dest: /etc/ssl/local/certs/ + mode: 0644 + owner: root + group: root + when: client_key_file.changed - # @AB TODO: using OpenSSL CA serial file does not always generate unique serial when running playbook against multiple hosts - - name: Sign client cert request with CA - local_action: - module: | - shell if [ ! -e {{cert_dir}}/{{tls_client_cert}} ] - then - openssl x509 -req -sha256 -days {{tls_client_valid_days}} -CA {{cert_dir}}/{{tls_ca_cert}} -CAkey {{cert_dir}}/{{tls_ca_key}} \ - -set_serial {{ 999999999 | random }} -in {{cert_dir}}/{{tls_client_csr}} -out {{cert_dir}}/{{tls_client_cert}} -extfile {{cert_dir}}/{{tls_client_extfile}} - fi - args: - executable: /bin/bash - ignore_errors: true - run_once: true - when: generate_client_cert +- name: Check if the client CSR exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ tls_client_csr }}" + register: client_csr + +- name: Generate CSR and key for client cert + delegate_to: localhost + community.crypto.openssl_csr: + path: "{{ cert_dir }}/{{ tls_client_csr }}" + privatekey_path: "{{ cert_dir }}/{{ tls_client_key }}" + common_name: "{{ tls_client_commonname }}" + extended_key_usage: + - clientAuth + when: + - not client_csr.stat.exists + - generate_client_cert + +- name: Check if the client cert exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ tls_client_cert }}" + register: client_crt + +- name: Create and sign server cert request by CA + delegate_to: localhost + community.crypto.x509_certificate: + path: "{{ cert_dir }}/{{ tls_client_cert }}" + csr_path: "{{ cert_dir }}/{{ tls_client_csr }}" + ownca_not_after: "+{{ tls_client_valid_days }}d" + ownca_path: "{{ cert_dir }}/{{ tls_ca_cert }}" + ownca_privatekey_path: "{{ cert_dir }}/{{ tls_ca_key }}" + provider: ownca + when: + - not client_crt.stat.exists + - generate_client_cert + register: client_cert_file + +- name: Copy the certificate to the remote machine + become: yes + copy: + src: "{{ cert_dir }}/{{ tls_client_cert }}" + dest: /etc/ssl/local/private + mode: 0600 + owner: root + group: root + when: client_cert_file.changed diff --git a/tasks/generate-server-cert.yaml b/tasks/generate-server-cert.yaml index c35300f..5808241 100644 --- a/tasks/generate-server-cert.yaml +++ b/tasks/generate-server-cert.yaml @@ -1,38 +1,96 @@ --- - # Generate server cert - - name: Create CSR for server cert - local_action: - module: | - shell if [ ! -e {{cert_dir}}/{{inventory_hostname_short}}.csr ] - then - openssl req -newkey rsa:{{tls_server_key_size}} -nodes -subj "/CN={{inventory_hostname}}" \ - -keyout "{{cert_dir}}/{{inventory_hostname_short}}.key" -out "{{cert_dir}}/{{inventory_hostname_short}}.csr" - fi - args: - executable: /bin/bash - ignore_errors: true - when: generate_server_cert +- name: Ensure the custom directories to host certificates are present + become: yes + file: + state: directory + recurse: yes + path: "/etc/ssl/{{ item.path }}" + mode: "{{ item.mode }}" + owner: root + group: root + loop: + - {path: local/certs, mode: "0755"} + - {path: local/private, mode: "0700"} - - name: Generate certificate extensions file - local_action: - module: template - src: templates/server-cert-extfile.cnf.j2 - dest: "{{cert_dir}}/{{inventory_hostname_short}}-extfile.cnf" - when: - - generate_server_cert - - tls_server_enable_san +- name: Check if the server private key exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.key" + register: server_key - - name: Sign server cert request by CA - local_action: - module: | - shell if [ ! -e {{cert_dir}}/{{inventory_hostname_short}}.pem ] - then - openssl x509 -req -sha256 -days {{tls_server_valid_days}} \ - -CA "{{cert_dir}}/{{tls_ca_cert}}" -CAkey "{{cert_dir}}/{{tls_ca_key}}" -set_serial {{ 999999999 | random }} \ - -in "{{cert_dir}}/{{inventory_hostname_short}}.csr" -out "{{cert_dir}}/{{inventory_hostname_short}}.pem" {% if tls_server_enable_san %}-extfile "{{cert_dir}}/{{inventory_hostname_short}}-extfile.cnf"{% endif %} - - fi - args: - executable: /bin/bash - ignore_errors: true - when: generate_server_cert +- name: Create PEM private key for server + delegate_to: localhost + community.crypto.openssl_privatekey: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.key" + when: not server_key.stat.exists + register: server_key_file + +- name: Copy the key on the server + become: yes + copy: + src: "{{ cert_dir }}/{{ inventory_hostname_short }}.key" + dest: /etc/ssl/local/certs/ + mode: 0644 + owner: root + group: root + when: server_key_file.changed + +- name: Check if the server CSR exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.csr" + register: server_csr + +- name: Create CSR for server cert + delegate_to: localhost + community.crypto.openssl_csr: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.csr" + privatekey_path: "{{ cert_dir }}/{{ inventory_hostname_short }}.key" + common_name: "{{ inventory_hostname_short }}" + when: + - not server_csr.stat.exists + - generate_server_cert + - not tls_server_enable_san + +- name: Create CSR for server cert + delegate_to: localhost + community.crypto.openssl_csr: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.csr" + privatekey_path: "{{ cert_dir }}/{{ inventory_hostname_short }}.key" + common_name: "{{inventory_hostname_short}}" + subject_alt_name: "DNS:{{inventory_hostname}},DNS:{{inventory_hostname_short}},IP:{{(alt_interface_ip is defined) | ternary(alt_interface_ip, ansible_default_ipv4.address)}},IP:0.0.0.0,IP:127.0.0.1" + when: + - not server_csr.stat.exists + - generate_server_cert + - tls_server_enable_san + +- name: Check if the server cert exists + delegate_to: localhost + stat: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.pem" + register: server_crt + +- name: Create and sign server cert request by CA + delegate_to: localhost + community.crypto.x509_certificate: + path: "{{ cert_dir }}/{{ inventory_hostname_short }}.pem" + csr_path: "{{ cert_dir }}/{{ inventory_hostname_short }}.csr" + ownca_not_after: "+{{ tls_server_valid_days }}d" + ownca_path: "{{ cert_dir }}/{{ tls_ca_cert }}" + ownca_privatekey_path: "{{ cert_dir }}/{{ tls_ca_key }}" + provider: ownca + ignore_errors: true + when: + - not server_crt.stat.exists + - generate_server_cert + register: server_cert_file + +- name: Copy the certificate to the remote machine + become: yes + copy: + src: "{{ cert_dir }}/{{ inventory_hostname_short }}.pem" + dest: /etc/ssl/local/private + mode: 0600 + owner: root + group: root + when: server_cert_file.changed diff --git a/tasks/main.yml b/tasks/main.yml index 54579e6..9ea5934 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,20 +1,19 @@ --- # tasks file for generate-tls-certs +- name: Generate CA cert + include_tasks: generate-ca-cert.yaml + when: + - generate_tls_certs + - generate_ca_cert|bool - - name: Generate CA cert - import_tasks: generate-ca-cert.yaml - when: - - generate_tls_certs - - generate_ca_cert|bool +- name: Generate client cert + include_tasks: generate-client-cert.yaml + when: + - generate_tls_certs + - generate_client_cert|bool - - name: Generate client cert - import_tasks: generate-client-cert.yaml - when: - - generate_tls_certs - - generate_client_cert|bool - - - name: Generate server cert - import_tasks: generate-server-cert.yaml - when: - - generate_tls_certs - - generate_server_cert|bool +- name: Generate server cert + include_tasks: generate-server-cert.yaml + when: + - generate_tls_certs + - generate_server_cert|bool