Tworzenie certyfikatów z Ansible (dla leniuszków)

wiadrodanych.pl 3 lat temu
Zdjęcie: Ansible


Przetwarzanie danych wymaga softu, a soft trzeba zainstalować. Gdy skala rośnie konieczna jest automatyzacja dzięki Ansible, Puppet, Chef, Terraform i jeszcze to innych wynalazków. W tym artykule dowiesz się jak wygenerować niezliczoną ilość certyfikatów dzięki kilku kliknięć w Ansible.

Co to jest Ansible?

Otwierałeś/aś kiedyś 9 okienek w konsoli z opcją pisania jednocześnie na wszystkich naraz? Powiedzmy, iż taki właśnie problem rozwiązuje Ansible. Za jego pomocą możesz wykonać jakąś komendę/akcję na grupie maszyn. Wygląda to mniej więcej tak:

ansible -a 'echo elo mordeczko' localhost localhost | CHANGED | rc=0 >> elo mordeczko

Oczywiście to prosty przykład. Wskazałem grupę jako localhost. O inventory wspomnę później na przykładzie. Siłą Ansible są moduły, które wykonują konkretne operacje dla podanych parametrów.

Co to jest Ansible Playbook?

Rzadko kiedy pojedyncza operacja wystarczy. Musimy skopiować jakiś plik, potem go rozpakować, utworzyć użytkownika, utworzyć folder, przenieść pliki konfiguracyjne i jeszcze wiele innych rzeczy. Może chesz postawić klaster Apache Spark na 20 serwerach? Po to właśnie są playbook’i. Wykonujemy sekwencję operacji na wskazanych hostach. Poniżej przykład inspirowany z dokumentacji modułu file:

--- - name: Do something hosts: localhost tasks: - name: Create a directory if it does not exist ansible.builtin.file: path: /etc/some_directory state: directory mode: '0755' ---

Problem do rozwiązania

Instalujesz klaster Elasticsearch? Potrzebujesz certyfikaty. Instalujesz klaster Apache Kafka/Redpanda? Potrzebujesz certyfikaty. Chcesz wystawić Apache Zeppelin/Jupyter’a po TLS? Potrzebujesz certyfikatu lub CSR które podpisze Ci zaufany urząd certyfikacji (CA).

Nie wiem jak Ty, ale ja zawsze na nowo szukam zaklęć openssl, które rozwiążą mój problem. Postanowiłem utworzyć playbook, który będzie robił to za mnie.

Rozwiązanie

Inventory

Hosty, grupy i zmienne definiujemy w inventory. Pod ten przykład utworzyłem grupę elastic oraz vip_hosts. Niektóre hosty mają zmienne.

all: children: elastic: hosts: es-01: ansible_host: 10.0.0.10 some_var: "hey" es-02: ansible_host: 10.0.0.11 some_other_var: "hey you" es-03: ansible_host: 10.0.0.12 es-04: ansible_host: 10.0.0.13 es-05: ansible_host: 10.0.0.14 vip_hosts: hosts: host-01: ansible_host: 10.0.0.20 important_var: "oh no!" host-02: ansible_host: 10.0.0.21 important_var: "anyway..."

Jak dobrać się do adekwatności hostów?

Chcemy utworzyć certyfikaty dla grupy hostów, więc fajnie było by dobrać się do ich zmiennych i adekwatności. Wstępnie, wykorzystamy do tego moduł debug. Docelowy playbook chcę wykonywać na `localhost`, więc będzie trzeba iterować w trochę inny sposób niż “normalnie”.

--- - name: Debug inventory hosts: localhost vars: my_group: elastic tasks: - name: debug groups debug: msg: "{{ groups }}" - name: debug vip_hosts group debug: msg: "{{ groups['vip_hosts'] }}" - name: debug host es-2 debug: msg: "{{ hostvars['es-02'] }}" - name: debug each host of my_group debug: msg: "{{ hostvars[item] }}" with_items: "{{ groups[my_group] }}" ...

Przeanalizujmy to co powyżej. Po wykonaniu ansible-playbook -i inventory.yml debug.yml, task debug groups wyrzuci nam kolekcje grup i ich elementy zdefiniowane w inventory.yml.

TASK [debug groups] *********************************************************************************************************************************************** ok: [localhost] => { "msg": { "all": [ "es-01", "es-02", "es-03", "es-04", "es-05", "host-01", "host-02" ], "elastic": [ "es-01", "es-02", "es-03", "es-04", "es-05" ], "ungrouped": [], "vip_hosts": [ "host-01", "host-02" ] } }

Kolejny odniesie się tylko do grupy vip_hosts.

TASK [debug vip_hosts group] ************************************************************************************************************************************** ok: [localhost] => { "msg": [ "host-01", "host-02" ] }

hostvars da nam możliwość odniesienia się do konkretnej adekwatności danego hosta.

ok: [localhost] => { "msg": { "ansible_check_mode": false, "ansible_diff_mode": false, "ansible_facts": {}, "ansible_forks": 5, "ansible_host": "10.0.0.11", "ansible_inventory_sources": [ "/home/maciej/ansible-playbook-certificate-generator/inventory.yml" ], "ansible_playbook_python": "/usr/bin/python3", "ansible_run_tags": [ "all" ], "ansible_skip_tags": [], "ansible_verbosity": 0, "ansible_version": { "full": "2.9.6", "major": 2, "minor": 9, "revision": 6, "string": "2.9.6" }, "group_names": [ "elastic" ], "groups": { "all": [ "es-01", "es-02", "es-03", "es-04", "es-05", "host-01", "host-02" ], "elastic": [ "es-01", "es-02", "es-03", "es-04", "es-05" ], "ungrouped": [], "vip_hosts": [ "host-01", "host-02" ] }, "inventory_dir": "/home/maciej/ansible-playbook-certificate-generator", "inventory_file": "/home/maciej/ansible-playbook-certificate-generator/inventory.yml", "inventory_hostname": "es-02", "inventory_hostname_short": "es-02", "playbook_dir": "/home/maciej/ansible-playbook-certificate-generator", "some_other_var": "hey you" } }

Wykorzystując hostvars, groups, zmienną my_group oraz pętlę with_items możemy dobrać się do każdego hosta danej grupy.

TASK [debug each host of my_group] ******************************************************************************************************************************** ok: [localhost] => (item=es-01) => { "msg": { ... } } ok: [localhost] => (item=es-02) => { "msg": { ... } } ok: [localhost] => (item=es-03) => { "msg": { ... } } ok: [localhost] => (item=es-04) => { "msg": { ... } } ok: [localhost] => (item=es-05) => { "msg": { ... } }

Generowanie CA

Najpierw musimy wygenerować nasz urząd certyfikacji (no, chyba iż już go masz). Poniżej cały playbook. Zmienne są raczej oczywiste. Pierwszy task tworzy foldery, a kolejne generuję klucz, CSR, certyfikat w formacie PEM i PKCS#12. W skrypcie jest sporo zmiennych, ale wydają mi się w miarę czytelne.

--- - name: Create CA hosts: localhost vars: certs_dir: "{{ playbook_dir }}/my_certs" country: "PL" common_name: "My Awesome CA" organization_name: "wiaderko" tasks: - name: create folders file: path: "{{ item }}" state: directory loop: - "{{ certs_dir }}" - "{{ certs_dir }}/ca" - name: create CA key openssl_privatekey: path: "{{ certs_dir }}/ca/ca.key" register: ca_key - name: create the CA CSR openssl_csr: path: "{{ certs_dir }}/ca/ca.csr" privatekey_path: "{{ ca_key.filename }}" country_name: "{{ country }}" common_name: "{{ common_name }}" organization_name: "{{ organization_name }}" basic_constraints: ["CA:TRUE"] register: ca_csr - name: sign the CA CSR openssl_certificate: path: "{{ certs_dir }}/ca/ca.crt" csr_path: "{{ ca_csr.filename }}" privatekey_path: "{{ ca_key.filename }}" provider: selfsigned register: ca_crt - name: create PKCS file openssl_pkcs12: action: export path: "{{ certs_dir }}/ca/ca.p12" friendly_name: "{{ common_name }}" privatekey_path: "{{ ca_key.filename }}" certificate_path: "{{ certs_dir }}/ca/ca.crt" state: present ...

Po uruchomieniu ansible-playbook -i inventory.yml create_ca.yml widzimy utworzone pliki:

tree . . ├── README.md ├── create_ca.yml ├── debug.yml ├── inventory.yml └── my_certs └── ca ├── ca.crt ├── ca.csr ├── ca.key └── ca.p12

Są choćby zjadliwe przez openssl’a:

openssl x509 -in my_certs/ca/ca.crt -text -noout Certificate: Data: Version: 3 (0x2) Serial Number: 20:d9:7c:81:8d:93:2c:bf:7a:0d:bc:0a:9f:82:12:a8:5b:6b:fb:40 Signature Algorithm: sha256WithRSAEncryption Issuer: C = PL, O = wiaderko, CN = My Awesome CA Validity Not Before: Nov 14 18:53:09 2021 GMT Not After : Nov 12 18:53:09 2031 GMT Subject: C = PL, O = wiaderko, CN = My Awesome CA Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (4096 bit) Modulus: ...

Generowanie certyfikatów – wersja 1

Zabieramy się za generowanie certyfikatów. Tym na białym koniu wjedzie with_items gdzie będą iterowane kolejne hosty.

Naszym oczom powinno pojawić się coś podobnego:

➜ ansible-playbook-certificate-generator git:(main) ✗ ansible-playbook -i inventory.yml create_certs_v1.yml PLAY [Create certs for the group] ********************************************************************************************************************************* TASK [Gathering Facts] ******************************************************************************************************************************************** ok: [localhost] TASK [create dirs] ************************************************************************************************************************************************ changed: [localhost] => (item=es-01) changed: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) changed: [localhost] => (item=es-04) changed: [localhost] => (item=es-05) TASK [create host certificate key] ******************************************************************************************************************************** changed: [localhost] => (item=es-01) changed: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) changed: [localhost] => (item=es-04) changed: [localhost] => (item=es-05) TASK [create the CSR] ********************************************************************************************************************************************* changed: [localhost] => (item=es-01) changed: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) changed: [localhost] => (item=es-04) changed: [localhost] => (item=es-05) TASK [sign the CSR with CA] *************************************************************************************************************************************** changed: [localhost] => (item=es-01) changed: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) changed: [localhost] => (item=es-04) changed: [localhost] => (item=es-05) TASK [create PKCS file] ******************************************************************************************************************************************* changed: [localhost] => (item=es-01) changed: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) changed: [localhost] => (item=es-04) changed: [localhost] => (item=es-05) PLAY RECAP ******************************************************************************************************************************************************** localhost : ok=6 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Po odpaleniu openssl x509 -in my_certs/es-02/cert.crt -text -noout zobaczymy informacje jednego z utworzonych certyfikatów. W tym przypadku nie podawaliśmy Subject Alternative Name (SAN), więc przypisana została wartość Common Name (CN).

X509v3 extensions: X509v3 Subject Alternative Name: DNS:es-02

Certyfikaty możemy zweryfikować openssl’em.

openssl verify -CAfile ./my_certs/ca/ca.crt ./my_certs/es-02/cert.crt ./my_certs/es-02/cert.crt: OK

Generowanie certyfikatów – wersja 2 (SAN)

Czasami wystawiamy serwis pod wieloma nazwami domenowymi lub adresami IP, stąd potrzebne jest zdefiniowanie Subject Alternative Name. Dodajmy więc do jednego z host’ów parametr z listą SAN.

... es-03: ansible_host: 10.0.0.12 san: - IP:10.0.0.12 - IP:11.11.11.11 - DNS:wiaderko.pl - DNS:ansible-fanboy.pl ...

Wpisywanie manualnie SAN wszystkim hostom w inventory było by czasochłonenne i nudne. Wykorzystamy default(omit), czyli parametr ten będzie pomijany w przypadku braku zmiennej san.

TASK [create the CSR] ********************************************************************************************************************************************* ok: [localhost] => (item=es-01) ok: [localhost] => (item=es-02) changed: [localhost] => (item=es-03) ok: [localhost] => (item=es-04) ok: [localhost] => (item=es-05)

Ponowne odpalenie playbooka spowodowało zmianę tylko hosta z dodanym SAN’em.

... X509v3 extensions: X509v3 Subject Alternative Name: IP Address:10.0.0.12, IP Address:11.11.11.11, DNS:wiaderko.pl, DNS:ansible-fanboy.pl ...

A efekt jest widoczny powyżej .

Podsumowanie

Baw się i używaj Ansible. Można zautomatyzować sporo powtarzalnych i nudnych zadań. Każdy wie, iż lepiej poświecić 6 godzin na automatyzacje zadania które zajmuje nam 3 minuty raz do roku .

xkcd: Automation

Repozytorium

zorteran/ansible-playbook-certificate-generator (github.com)

Idź do oryginalnego materiału