Предыдущий пост серии — Ansible: миграция RTFM 2.9 – монтирование EBS и настройка NGINX на Bastion.
Сегодня надо выполнить установку и настройку:
- Let’s Encrypt
- SSL на NGINX и виртуалхоста (пока только dev.rtfm.co.ua)
hostnameexim
Ссылки на коммиты файлов, получившиеся в результате написания этого поста:
roles/letsencrypt/tasks/main.ymlrtfm-blog-ansible-bastion-provision.ymlroles/nginx/templates/nginx.confhostsroles/common/tasks/main.ymlroles/exim/templates/update-exim4.conf.conf.j2roles/exim/templates/mailname.j2roles/exim/tasks/main.yml
Содержание
Let’s Encrypt
Для Let’s Encrypt напишем свою роль, которая будет только выполнять установку клиента и добавлять cron-задачу для обновления сертификатов.
Сами сертификаты и все настройки Let’s Encrypt будут храниться на data-диске — EBS-разделе, который подключается к EC2 во время создания стека:
[simterm]
root@ip-10-0-1-19:/home/admin# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 8G 0 disk └─xvda1 202:1 0 8G 0 part / xvdb 202:16 0 8G 0 disk └─xvdb1 202:17 0 8G 0 part /data
[/simterm]
Сейчас в каталоге /data размещаются только конфиги NGINX:
[simterm]
root@ip-10-0-1-19:/home/admin# ls -l /data/* /data/data-nginx.d: total 4 -rwxr-xr-x 1 root root 324 Feb 3 13:49 dev.rtfm.co.ua.conf -rwxr-xr-x 1 root root 0 Feb 3 14:38 test.file /data/lost+found: total 0
[/simterm]
На рабочей машине создаём каталог для роли Let’s Encrypt:
[simterm]
$ mkdir -p roles/letsencrypt/tasks
[/simterm]
Для установки потребуется:
- создать каталог
/data/letsencrypt— он будет использоваться вместо дефолтного/etc/letsecrypt(опция--config-dir), если его нет - установить клиент
letsencrypt - добавить
cron-задачу дляrenew
Создаём файл roles/letsencrypt/tasks/main.yml, добавляем создание каталога:
- name: Create Let's Encrypt directory
file:
path=/data/letsencrypt
state=directory
И установку Let’s Encrypt клиента (certbot):
...
- name: Install Let's Encrypt client
apt:
name=letsencrypt
state=latest
В файл плейбука rtfm-blog-ansible-bastion-provision.yml добавляем вызов роли после роли common (в которой выполняется подключение раздела /dev/xvdb1):
- hosts: all
become:
true
roles:
- role: common
- role: letsencrypt
...
Проверяем:
[simterm]
$ ansible-playbook --syntax-check --limit=rtfm-bastion-dev rtfm-blog-ansible-bastion-provision.yml
playbook: rtfm-blog-ansible-bastion-provision.yml
[/simterm]
Запускаем:
[simterm]
$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml PLAY [all] **** TASK [Gathering Facts] **** ok: [dev.rtfm.co.ua] TASK [common : Create "/data" directory] **** ok: [dev.rtfm.co.ua] TASK [common : Mount data-volume "/dev/xvdb1" to "/data"] **** ok: [dev.rtfm.co.ua] TASK [common : Install common packages] **** ok: [dev.rtfm.co.ua] => (item=[u'mailutils', u'curl']) TASK [letsencrypt : Create Let's Encrypt certs directory] **** changed: [dev.rtfm.co.ua] TASK [letsencrypt : Install Let's Encrypt client] **** changed: [dev.rtfm.co.ua] ...
[/simterm]
Проверяем каталог:
[simterm]
root@ip-10-0-1-19:/home/admin# ls -l /data/letsencrypt/ total 0
[/simterm]
Клиент:
[simterm]
root@ip-10-0-1-19:/home/admin# letsencrypt --version certbot 0.10.2
[/simterm]
ОК — попробуем получить сертификат с опциями:
--config-dir: путь к каталогу с настройками и файлами ключей--noninteractive: не запрашивать ввода данных от пользователя--webroot: авторизация черезwebroot:--webroot-path— путь кlocation .well-known
--email: почта для уведомлений и восстановления--agree-tos: согласиться с ACME Subscriber Agreement
[simterm]
root@ip-10-0-1-19:/home/admin# letsencrypt certonly --config-dir /data/letsencrypt/ --noninteractive --webroot --webroot-path /var/www/html/ --email [email protected] --agree-tos --domains dev.rtfm.co.ua Saving debug log to /var/log/letsencrypt/letsencrypt.log Obtaining a new certificate Performing the following challenges: http-01 challenge for dev.rtfm.co.ua Using the webroot path /var/www/html for all unmatched domains. Waiting for verification... Cleaning up challenges Generating key (2048 bits): /data/letsencrypt/keys/0000_key-certbot.pem Creating CSR: /data/letsencrypt/csr/0000_csr-certbot.pem Non-standard path(s), might not work with crontab installed by your operating system package manager IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem. Your cert will expire on 2018-05-11. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" ...
[/simterm]
Отлично — работает.
Проверям сертификаты:
[simterm]
root@ip-10-0-1-19:/home/admin# certbot --config-dir /data/letsencrypt/ certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
-------------------------------------------------------------------------------
Found the following certs:
Certificate Name: dev.rtfm.co.ua
Domains: dev.rtfm.co.ua
Expiry Date: 2018-05-11 11:39:00+00:00 (VALID: 89 days)
Certificate Path: /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem
Private Key Path: /data/letsencrypt/live/dev.rtfm.co.ua/privkey.pem
-------------------------------------------------------------------------------
[/simterm]
Проверяем каталог с данными:
[simterm]
root@ip-10-0-1-19:/home/admin# ls -l /data/letsencrypt/* /data/letsencrypt/accounts: total 4 drwx------ 3 root root 4096 Feb 10 12:38 acme-v01.api.letsencrypt.org /data/letsencrypt/archive: total 4 drwxr-xr-x 2 root root 4096 Feb 10 12:39 dev.rtfm.co.ua /data/letsencrypt/csr: total 4 -rw-r--r-- 1 root root 956 Feb 10 12:38 0000_csr-certbot.pem /data/letsencrypt/keys: total 4 -rw------- 1 root root 1704 Feb 10 12:38 0000_key-certbot.pem /data/letsencrypt/live: total 4 drwxr-xr-x 2 root root 4096 Feb 10 12:39 dev.rtfm.co.ua /data/letsencrypt/renewal: total 4 -rw-r--r-- 1 root root 585 Feb 10 12:39 dev.rtfm.co.ua.conf
[/simterm]
ОК — теперь можно пересоздавать стек, а сертификаты будут на прежнем месте — на EBS разделе.
Добавим в роль letsencrypt создание cron-задачи.
Проверяем работу renew:
[simterm]
root@ip-10-0-1-19:/home/admin# certbot --config-dir /data/letsencrypt/ renew Saving debug log to /var/log/letsencrypt/letsencrypt.log ------------------------------------------------------------------------------- Processing /data/letsencrypt/renewal/dev.rtfm.co.ua.conf ------------------------------------------------------------------------------- Cert not yet due for renewal The following certs are not due for renewal yet: /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem (skipped) No renewals were attempted.
[/simterm]
Возвращаемся к roles/letsencrypt/tasks/main.yml:
...
- name: Add Let's Encrypt cronjob for cert renewal
cron:
name: letsencrypt_renewal
special_time: weekly
job: letsencrypt --config-dir /data/letsencrypt/ renew & > /var/log/letsencrypt/letsencrypt.log && service nginx reload
Запускаем:
[simterm]
$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml
[/simterm]
Проверяем:
[simterm]
root@ip-10-0-1-19:/home/admin# crontab -l #Ansible: letsencrypt_renewal @weekly letsencrypt --config-dir /data/letsencrypt/ renew & > /var/log/letsencrypt/letsencrypt.log && service nginx reload
[/simterm]
NGINX SSL
Обновим настройки самого NGINX, добавим параметры SSL (см. пост OpenBSD: установка NGINX и настройки безопасности).
Сначала выполним на сервер, проверим, потом обновим roles/nginx/templates/nginx.conf и шаблон виртуалхоста в Bitbucket.
Отключаем афиширование версии NGINX:
... server_tokens off; ...
Обновляем параметры SSL:
...
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH !RC4";
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
ssl_stapling on;
ssl_stapling_verify on;
...
Обновляем параметры SSL для виртуалхоста dev.rtfm.co.ua (сам шаблон хранится в приватном репозитории Bibucket).
Добавляем HSTS:
... add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; ...
Отключаем проверку MIME-типов — добавляем X-Content-Type-Options:
... add_header X-Content-Type-Options nosniff; ...
Включаем SSL:
... listen 443 ssl; ... ssl_certificate /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem; ssl_certificate_key /data/letsencrypt/live/dev.rtfm.co.ua/privkey.pem; ...
Добавляем новый server {} на порту 80, и выполняем редирект на 443:
server {
listen 80;
server_name dev.rtfm.co.ua;
return 301 https://dev.rtfm.co.ua$request_uri;
}
...
Полностью файл /data/data-nginx.d/dev.rtfm.co.ua.conf сейчас выглядит так:
server {
listen 80;
server_name dev.rtfm.co.ua;
return 301 https://dev.rtfm.co.ua$request_uri;
}
server {
server_name dev.rtfm.co.ua;
listen 443 ssl;
access_log /var/log/nginx/dev.rtfm.co.ua-access.log proxy;
error_log /var/log/nginx/dev.rtfm.co.ua-error.log notice;
ssl_certificate /data/letsencrypt/live/dev.rtfm.co.ua/fullchain.pem;
ssl_certificate_key /data/letsencrypt/live/dev.rtfm.co.ua/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
add_header X-Content-Type-Options nosniff;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location ~ /.well-known {
allow all;
}
location / {
try_files $uri $uri/ =404;
}
}
Проверяем, перечитываем конфиги:
[simterm]
root@ip-10-0-1-19:/etc/nginx# nginx -t && service nginx reload nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
[/simterm]
Проверяем:
[simterm]
$ curl -Iv dev.rtfm.co.ua
* Rebuilt URL to: dev.rtfm.co.ua/
* Trying 34.243.86.146...
* TCP_NODELAY set
* Connected to dev.rtfm.co.ua (34.243.86.146) port 80 (#0)
> HEAD / HTTP/1.1
> Host: dev.rtfm.co.ua
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
HTTP/1.1 301 Moved Permanently
< Server: nginx
Server: nginx
< Date: Sat, 10 Feb 2018 13:20:01 GMT
Date: Sat, 10 Feb 2018 13:20:01 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 178
Content-Length: 178
< Connection: keep-alive
Connection: keep-alive
< Location: https://dev.rtfm.co.ua/
Location: https://dev.rtfm.co.ua/
<
* Connection #0 to host dev.rtfm.co.ua left intact
[/simterm]
Всё хорошо — редирект работает, SSL тоже:
Проверяем уровень на https://www.ssllabs.com/ssltest/:
B — а хочется A+, сейчас исправим — добавим генерацию ключа Diffie-Hellman.
Обновляем роль nginx, в roles/nginx/tasks/main.yml добавляем генерацию ключа:
...
- name: Generate dhparams
shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048
args:
creates: /etc/nginx/dhparams.pem
...
Обновляем roles/nginx/templates/nginx.conf аналогично тому, как делали на сервере, но добавляем:
... ssl_dhparam /etc/nginx/dhparams.pem; ...
Обновляем шаблон ~/Work/RTFM/Bitbucket/rtfm-blog-ansible-templates/dev.rtfm.co.ua.conf (сейчас шаблон копируется из роли nginx, но надо будет создать отдельную задачу в Jenkins для обновления конфигов виртуалхостов NGINX).
Проверяем:
[simterm]
$ git add rtfm-bansible-playbook --syntax-check --limit=rtfm-bastion-dev rtfm-blog-ansible-bastion-provision.yml playbook: rtfm-blog-ansible-bastion-provision.yml
[/simterm]
Запускаем:
[simterm]
$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml ... TASK [nginx : Install Nginx] **** ok: [dev.rtfm.co.ua] TASK [nginx : Replace NGINX config] **** changed: [dev.rtfm.co.ua] TASK [nginx : Generate dhparams] **** changed: [dev.rtfm.co.ua] TASK [nginx : Service NGINX reload] **** changed: [dev.rtfm.co.ua] ...
[/simterm]
Проверяем:
ОК — есть А+.
Наверное — стоит всё-таки сейчас удалить весь стек, пересоздать его заново, и проверить, что всё работает. Потом можно добавить настройку hostname и exim.
Удаляем CloudFormation стек:
[simterm]
$ aws cloudformation delete-stack --stack-name rtfm-dev
[/simterm]
Создаём его заново:
[simterm]
$ cd ~/Work/RTFM/Github/rtfm-blog-cf-templates/ $ aws cloudformation --region eu-west-1 create-stack --stack-name rtfm-dev --template-body file://rtfm-blog-cf-template.json --parameters ParameterKey=HomeAllowLocation,ParameterValue=188.***.***.114/32 ParameterKey=JenkinsIP,ParameterValue=52.***.***.34/32
[/simterm]
Запускаем Ansible:
[simterm]
$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem --extra-vars api_key=967***e31 rtfm-blog-ansible-bastion-provision.yml
Проверяем:
[simterm]
$ curl -IL dev.rtfm.co.ua HTTP/1.1 301 Moved Permanently Server: nginx Date: Sat, 10 Feb 2018 14:22:37 GMT Content-Type: text/html Content-Length: 178 Connection: keep-alive Location: https://dev.rtfm.co.ua/ HTTP/1.1 200 OK Server: nginx Date: Sat, 10 Feb 2018 14:22:37 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Sat, 10 Feb 2018 14:10:37 GMT Connection: keep-alive ETag: "5a7efd5d-264" Strict-Transport-Security: max-age=31536000; includeSubdomains X-Content-Type-Options: nosniff Accept-Ranges: bytes
[/simterm]
Всё работает.
hostname
Для корректной работы почты — надо настроить hostname, см. детали в посте Exim: Mailing to remote domains not supported.
Добавим переменную set_ostname в файл hosts:
... [rtfm-bastion-dev:vars] data_volume_id="/dev/xvdb1" set_hostname="rtfm-bastion-dev" ...
Обновляем роль common, в файле roles/common/tasks/main.yml используем модуль hostname:
...
- name: Set hostname
hostname:
name: "{{ set_hostname }}"
Для обновления файла /etc/hosts — используем модуль lineinfile, добавляем его тут же, следующим:
...
- name: Add hostnames to /etc/hosts
lineinfile:
dest: /etc/hosts
regexp: '^127\.0\.0\.1[ \t]+localhost'
line: "127.0.0.1 localhost {{ set_hostname }} {{ inventory_hostname }}"
state: present
Т.к. это EC2 — то файл /etc/hosts обновляется при рестартах:
[simterm]
root@ip-10-0-1-15:/home/admin# head /etc/hosts # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # 127.0.1.1 ip-10-0-1-15.eu-west-1.compute.internal ip-10-0-1-15 127.0.0.1 localhost
[/simterm]
Находим параметр:
[simterm]
root@ip-10-0-1-15:/home/admin# grep -r manage_etc_hosts /etc/ /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg:manage_etc_hosts: true
[/simterm]
Добавляем изменения для /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg:
...
- name: Update /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg
lineinfile:
dest: /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg
regexp: '^manage_etc_hosts: true'
line: "manage_etc_hosts: false"
state: present
Запускаем:
[simterm]
$ ansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml
[/simterm]
Проверяем:
[simterm]
root@ip-10-0-1-15:/home/admin# cat /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg | grep manage manage_etc_hosts: false
[/simterm]
exim
Последняя задача на сегодня — настроить exim на отправку почты «в мир».
Для этого надо изменить два файла — /etc/exim4/update-exim4.conf.conf и /etc/mailname.
Создадим роль exim:
[simterm]
$ mkdir -p roles/exim/{tasks,templates}
[/simterm]
В templates добавим шаблон update-exim4.conf.conf.j2:
dc_eximconfig_configtype='internet'
dc_other_hostnames='"{{ inventory_hostname }}"'
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost=''
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost=''
CFILEMODE='644'
dc_use_split_config='false'
dc_hide_mailname=''
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'
И mailname.j2 с одной строкой:
{{ inventory_hostname }}
В roles/exim/tasks/main.yml описываем применение шаблонов, перезагрузку exim и отправку тестового сообщения:
- name: Update Exim4 settings
template:
src=templates/update-exim4.conf.conf.j2
dest=/etc/exim4/update-exim4.conf.conf
- name: Update mailname
template:
src=templates/mailname.j2
dest=/etc/mailname
- name: Exim4 restart
service:
name=exim4
state=restarted
- name: Send test email
shell:
echo "Eximt4 config complete" | mailx -s "{{ inventory_hostname }} Exim4 test" [email protected]
Добавляем роль exim в rtfm-blog-ansible-bastion-provision.yml:
- hosts: all
become:
true
roles:
- role: common
- role: exim
...
Запускаем:
[simterm]
$ vim roles/eximansible-playbook --limit=rtfm-bastion-dev --private-key=../../Bitbucket/aws-credentials/rtfm-dev.pem rtfm-blog-ansible-bastion-provision.yml ... TASK [common : Set hostname] **** ok: [dev.rtfm.co.ua] TASK [common : Add hostnams to /etc/hosts] **** ok: [dev.rtfm.co.ua] TASK [common : Update /etc/cloud/cloud.cfg.d/01_debian_cloud.cfg] **** changed: [dev.rtfm.co.ua] TASK [exim : Update Exim4 settings] **** changed: [dev.rtfm.co.ua] TASK [exim : Update mailname] **** changed: [dev.rtfm.co.ua] TASK [exim : Exim4 restart] **** changed: [dev.rtfm.co.ua] TASK [exim : Send test email] **** changed: [dev.rtfm.co.ua] ...
[/simterm]
Проверяем:
[simterm]
root@ip-10-0-1-15:/home/admin# cat /var/log/exim4/mainlog | grep notify 2018-02-10 15:01:45 1ekWet-0004rj-Pz => [email protected] R=dnslookup T=remote_smtp H=mail.domain.kiev.ua [77.***.***.20] X=TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256 CV=yes DN="CN=mail.domain.kiev.ua" C="250 OK id=1ekWeu-0001lI-IU"
Готово.
Ещё надо обновить почту root, добавить копирование .bashrc, .vimrc и установку fail2ban — но это уже в следующий раз.




