chore: реализована работа с самоподписными сертификатами
This commit is contained in:
1 parent
9bb3a2fc5d
commit
602199d510
23 files changed
+524
-108
No files matched your search
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Порт ssh меняется в два шага, чтобы не потерять сервер:
|
||||
# сначала sshd слушает старый и новый порт, после проверки входа по ключу на новом порту старый закрывается.
|
||||
# Если обновление системы что-то поставило, в конце сервер перезагружается: подхватывает новое ядро и библиотеки,
|
||||
# заодно проверяется, что ssh и nftables переживают перезагрузку. В контейнере перезагрузки нет.
|
||||
|
||||
- name: Prepare fresh server (password login)
|
||||
hosts: target
|
||||
@@ -9,12 +11,12 @@
|
||||
vars:
|
||||
ansible_password: "{{ login_password }}"
|
||||
tasks:
|
||||
- name: Require Debian 13
|
||||
- name: Require Debian 12 or 13
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == 'Debian'
|
||||
- ansible_facts.distribution_major_version == '13'
|
||||
fail_msg: "Supported only Debian 13, got {{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}"
|
||||
- ansible_facts.distribution_major_version in ['12', '13']
|
||||
fail_msg: "Supported only Debian 12 and 13, got {{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}"
|
||||
|
||||
- name: Create .ssh directory
|
||||
ansible.builtin.file:
|
||||
@@ -35,6 +37,7 @@
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
upgrade: dist
|
||||
register: system_upgrade
|
||||
|
||||
- ansible.builtin.import_tasks: tasks/firewall.yml
|
||||
vars:
|
||||
@@ -70,3 +73,10 @@
|
||||
- name: Check hardened ssh
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 60
|
||||
|
||||
- name: Reboot after upgrade
|
||||
ansible.builtin.reboot:
|
||||
reboot_timeout: 600
|
||||
when:
|
||||
- system_upgrade is changed
|
||||
- ansible_facts.virtualization_type not in ['docker', 'podman', 'container', 'containerd']
|
||||
@@ -33,10 +33,25 @@
|
||||
ansible.builtin.command: sysctl --system
|
||||
changed_when: false
|
||||
|
||||
- name: Remove DNS over TLS config
|
||||
- name: Remove encrypted DNS config
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d/00-serverus.conf
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /etc/systemd/resolved.conf.d/00-serverus.conf
|
||||
- /etc/systemd/network/90-serverus-dns.netdev
|
||||
- /etc/systemd/network/90-serverus-dns.network
|
||||
|
||||
- name: Remove DNS fallback interface
|
||||
ansible.builtin.command: ip link delete serverus-dns
|
||||
register: dns_link
|
||||
changed_when: dns_link.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Stop dnscrypt-proxy
|
||||
ansible.builtin.command: systemctl disable --now dnscrypt-proxy.socket dnscrypt-proxy.service
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
# systemd-resolved остается: без него /etc/resolv.conf указывает в никуда
|
||||
- name: Restart systemd-resolved
|
||||
|
||||
@@ -1,10 +1,74 @@
|
||||
# Системный резолвер: systemd-resolved, только DNS-over-TLS к Cloudflare.
|
||||
# Строгий режим: без TLS запрос не уйдет, открытым текстом DNS не утекает. Резервных серверов нет.
|
||||
- name: Install systemd-resolved
|
||||
# Системный резолвер: systemd-resolved с двумя путями до Cloudflare, оба шифрованные.
|
||||
#
|
||||
# Основной - DNS-over-TLS (853), строгий режим: без TLS запрос не уйдет, открытым текстом DNS не утекает.
|
||||
# Запасной - dnscrypt-proxy с DNS-over-HTTPS (443) на 127.0.2.1, если 853 режут. resolved спрашивает оба пути
|
||||
# параллельно и берет первый успешный ответ. Режим TLS в resolved один на область, поэтому запасной путь живет
|
||||
# на отдельном dummy интерфейсе serverus-dns со своими настройками. Резервных серверов в открытом виде нет.
|
||||
# Адрес интерфейса из TEST-NET (192.0.2.0/24) с глобальной областью: интерфейс только с link-local адресом
|
||||
# resolved для DNS не использует. Проверено: при закрытом 853 ответ приходит через DoH за доли секунды.
|
||||
- name: Install DNS packages
|
||||
ansible.builtin.apt:
|
||||
name: systemd-resolved
|
||||
name: [ systemd-resolved, dnscrypt-proxy, bind9-dnsutils ]
|
||||
state: present
|
||||
|
||||
- name: Configure dnscrypt-proxy
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/dnscrypt-proxy/dnscrypt-proxy.toml
|
||||
mode: "0644"
|
||||
content: |
|
||||
# слушает сокет dnscrypt-proxy.socket (127.0.2.1:53), списки серверов не качает
|
||||
listen_addresses = []
|
||||
server_names = ['cloudflare-1', 'cloudflare-2']
|
||||
ipv4_servers = true
|
||||
ipv6_servers = false
|
||||
doh_servers = true
|
||||
dnscrypt_servers = false
|
||||
require_dnssec = false
|
||||
require_nolog = false
|
||||
require_nofilter = false
|
||||
cache = true
|
||||
|
||||
[static]
|
||||
[static.'cloudflare-1']
|
||||
stamp = 'sdns://AgMAAAAAAAAABzEuMS4xLjEAEmNsb3VkZmxhcmUtZG5zLmNvbQovZG5zLXF1ZXJ5'
|
||||
[static.'cloudflare-2']
|
||||
stamp = 'sdns://AgMAAAAAAAAABzEuMC4wLjEAEmNsb3VkZmxhcmUtZG5zLmNvbQovZG5zLXF1ZXJ5'
|
||||
register: dnscrypt_config
|
||||
|
||||
- name: Restart dnscrypt-proxy
|
||||
ansible.builtin.systemd_service:
|
||||
name: dnscrypt-proxy
|
||||
enabled: true
|
||||
state: restarted
|
||||
when: dnscrypt_config is changed
|
||||
|
||||
- name: Create dummy interface for DNS fallback
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/network/90-serverus-dns.netdev
|
||||
mode: "0644"
|
||||
content: |
|
||||
[NetDev]
|
||||
Name=serverus-dns
|
||||
Kind=dummy
|
||||
|
||||
- name: Route fallback DNS through dnscrypt-proxy
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/network/90-serverus-dns.network
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Match]
|
||||
Name=serverus-dns
|
||||
|
||||
[Network]
|
||||
Address=192.0.2.53/32
|
||||
LinkLocalAddressing=no
|
||||
ConfigureWithoutCarrier=yes
|
||||
DNS=127.0.2.1
|
||||
DNSOverTLS=no
|
||||
DNSSEC=no
|
||||
Domains=~.
|
||||
DNSDefaultRoute=yes
|
||||
|
||||
- name: Create resolved drop-in directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
@@ -21,6 +85,8 @@
|
||||
FallbackDNS=
|
||||
DNSOverTLS=yes
|
||||
Domains=~.
|
||||
LLMNR=no
|
||||
MulticastDNS=no
|
||||
|
||||
- name: Use resolved stub
|
||||
ansible.builtin.file:
|
||||
@@ -29,16 +95,31 @@
|
||||
state: link
|
||||
force: true
|
||||
|
||||
# networkd управляет только serverus-dns: остальные интерфейсы без .network файлов он не трогает
|
||||
- name: Start systemd-networkd
|
||||
ansible.builtin.systemd_service:
|
||||
name: systemd-networkd
|
||||
enabled: true
|
||||
state: restarted
|
||||
|
||||
- name: Restart systemd-resolved
|
||||
ansible.builtin.systemd_service:
|
||||
name: systemd-resolved
|
||||
enabled: true
|
||||
state: restarted
|
||||
|
||||
- name: Check DNS over TLS
|
||||
- name: Check DNS
|
||||
ansible.builtin.command: resolvectl query deb.debian.org
|
||||
register: dns_check
|
||||
retries: 5
|
||||
delay: 3
|
||||
until: dns_check.rc == 0
|
||||
changed_when: false
|
||||
|
||||
- name: Check DNS over HTTPS fallback
|
||||
ansible.builtin.command: dig +short +time=5 @127.0.2.1 deb.debian.org
|
||||
register: doh_check
|
||||
retries: 5
|
||||
delay: 3
|
||||
until: doh_check.rc == 0 and doh_check.stdout != ''
|
||||
changed_when: false
|
||||
@@ -10,7 +10,7 @@
|
||||
no_log: true
|
||||
|
||||
- name: Read panel address
|
||||
ansible.builtin.command: "{{ xui_bin }} setting -show true"
|
||||
ansible.builtin.command: "{{ xui_bin }} setting -show"
|
||||
register: xui_panel_settings
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
@@ -1,57 +1,83 @@
|
||||
# vars: cert_host - домен или ip, на который выпускается сертификат
|
||||
# vars: cert_host - домен или ip, на который выпускается сертификат;
|
||||
# cert_fullchain, cert_private_key (optional) - готовый сертификат, например от своего CA
|
||||
#
|
||||
# Сертификат Let's Encrypt через acme.sh в режиме standalone: проверка HTTP-01 на 80 порту, он должен быть открыт.
|
||||
# На ip выдается только короткий сертификат (~6 дней), acme.sh продлевает его по cron и перезапускает панель.
|
||||
# Ставит сертификат панели и подписке. Если выпустить не удалось, падает, а не оставляет панель на http.
|
||||
# Без готового сертификата - Let's Encrypt через acme.sh в режиме standalone: проверка HTTP-01 на 80 порту,
|
||||
# он должен быть открыт. На ip выдается только короткий сертификат (~6 дней), acme.sh продлевает его по cron
|
||||
# и перезапускает панель. Ставит сертификат панели. Если сертификата нет, падает, а не оставляет панель на http.
|
||||
- name: Set certificate paths
|
||||
ansible.builtin.set_fact:
|
||||
cert_is_ip: "{{ cert_host is match('^[0-9.]+$') or ':' in cert_host }}"
|
||||
cert_dir: "/root/cert/{{ cert_host }}"
|
||||
cert_given: "{{ cert_fullchain | default('') != '' }}"
|
||||
acme: /root/.acme.sh/acme.sh
|
||||
|
||||
- name: Install acme.sh dependencies
|
||||
ansible.builtin.apt:
|
||||
name: [ curl, socat, cron ]
|
||||
state: present
|
||||
|
||||
- name: Download acme.sh
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.acme.sh
|
||||
dest: /root/acme-install.sh
|
||||
mode: "0700"
|
||||
when: not acme is file
|
||||
|
||||
- name: Install acme.sh
|
||||
ansible.builtin.shell: sh /root/acme-install.sh
|
||||
args:
|
||||
chdir: /root
|
||||
creates: "{{ acme }}"
|
||||
|
||||
- name: Use Let's Encrypt
|
||||
ansible.builtin.command: "{{ acme }} --set-default-ca --server letsencrypt"
|
||||
changed_when: false
|
||||
|
||||
# код 2 - действующий сертификат уже есть, продлевать рано
|
||||
- name: Issue certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --issue -d {{ cert_host }} --standalone --httpport 80 --server letsencrypt
|
||||
{{ '--certificate-profile shortlived --days 6' if cert_is_ip else '' }}
|
||||
register: cert_issue
|
||||
changed_when: cert_issue.rc == 0
|
||||
failed_when: cert_issue.rc not in [0, 2]
|
||||
|
||||
- name: Create certificate directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ cert_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Install certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --installcert -d {{ cert_host }}
|
||||
--key-file {{ cert_dir }}/privkey.pem --fullchain-file {{ cert_dir }}/fullchain.pem
|
||||
--reloadcmd "systemctl restart x-ui"
|
||||
changed_when: true
|
||||
- name: Upload certificate
|
||||
when: cert_given
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ cert_dir }}/{{ item.name }}"
|
||||
content: "{{ item.content }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { name: fullchain.pem, content: "{{ cert_fullchain | default('') }}", mode: "0644" }
|
||||
- { name: privkey.pem, content: "{{ cert_private_key | default('') }}", mode: "0600" }
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
no_log: true
|
||||
|
||||
# иначе cron acme.sh перезапишет загруженный сертификат своим при продлении
|
||||
- name: Stop Let's Encrypt renewal
|
||||
when: cert_given and acme is file
|
||||
ansible.builtin.command: "{{ acme }} --remove -d {{ cert_host }}"
|
||||
register: acme_remove
|
||||
changed_when: acme_remove.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Issue Let's Encrypt certificate
|
||||
when: not cert_given
|
||||
block:
|
||||
- name: Install acme.sh dependencies
|
||||
ansible.builtin.apt:
|
||||
name: [ curl, socat, cron ]
|
||||
state: present
|
||||
|
||||
- name: Download acme.sh
|
||||
ansible.builtin.get_url:
|
||||
url: https://get.acme.sh
|
||||
dest: /root/acme-install.sh
|
||||
mode: "0700"
|
||||
when: not acme is file
|
||||
|
||||
- name: Install acme.sh
|
||||
ansible.builtin.shell: sh /root/acme-install.sh
|
||||
args:
|
||||
chdir: /root
|
||||
creates: "{{ acme }}"
|
||||
|
||||
- name: Use Let's Encrypt
|
||||
ansible.builtin.command: "{{ acme }} --set-default-ca --server letsencrypt"
|
||||
changed_when: false
|
||||
|
||||
# код 2 - действующий сертификат уже есть, продлевать рано
|
||||
- name: Issue certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --issue -d {{ cert_host }} --standalone --httpport 80 --server letsencrypt
|
||||
{{ '--certificate-profile shortlived --days 6' if cert_is_ip else '' }}
|
||||
register: cert_issue
|
||||
changed_when: cert_issue.rc == 0
|
||||
failed_when: cert_issue.rc not in [0, 2]
|
||||
|
||||
- name: Install certificate
|
||||
ansible.builtin.command: >-
|
||||
{{ acme }} --installcert -d {{ cert_host }}
|
||||
--key-file {{ cert_dir }}/privkey.pem --fullchain-file {{ cert_dir }}/fullchain.pem
|
||||
--reloadcmd "systemctl restart x-ui"
|
||||
changed_when: true
|
||||
|
||||
- name: Check certificate
|
||||
ansible.builtin.command: "openssl x509 -in {{ cert_dir }}/fullchain.pem -noout -checkend 3600 -ext subjectAltName"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# extravars: username, password, panel_port, web_base_path, sub_port, sub_path, domain (optional), ssl (optional),
|
||||
# version (optional)
|
||||
# extravars: username, password, panel_port, web_base_path, sub_port, sub_path, version, domain (optional),
|
||||
# ssl (optional), cert_fullchain и cert_private_key (optional, готовый сертификат вместо Let's Encrypt)
|
||||
#
|
||||
# version - тег релиза 3x-ui: скрипт установки берется из того же тега, панель ставится этой версии.
|
||||
# Ставит 3x-ui скриптом из README проекта в неинтерактивном режиме, SSL скрипта выключен: он при ошибке молча оставляет
|
||||
# панель на http. Сертификат выпускает tasks/xui_cert.yml на домен, если он задан, иначе на ip. Нужен открытый 80 порт.
|
||||
# ssl=false - без сертификата, для серверов без публичного адреса.
|
||||
@@ -22,14 +23,14 @@
|
||||
|
||||
- name: Download install script
|
||||
ansible.builtin.get_url:
|
||||
url: https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh
|
||||
url: "https://raw.githubusercontent.com/mhsanaei/3x-ui/{{ version }}/install.sh"
|
||||
dest: /root/3x-ui-install.sh
|
||||
mode: "0700"
|
||||
force: true
|
||||
|
||||
- name: Run install script
|
||||
ansible.builtin.command:
|
||||
cmd: "bash /root/3x-ui-install.sh {{ version | default('') }}"
|
||||
cmd: "bash /root/3x-ui-install.sh {{ version }}"
|
||||
creates: "{{ xui_bin }}"
|
||||
environment:
|
||||
XUI_NONINTERACTIVE: "1"
|
||||
|
||||
@@ -122,7 +122,8 @@
|
||||
|
||||
- name: Return inbound
|
||||
vars:
|
||||
stream: "{{ inbound.streamSettings | from_json }}"
|
||||
# список отдает строку JSON, а ответ на добавление - уже объект
|
||||
stream: "{{ inbound.streamSettings if inbound.streamSettings is mapping else inbound.streamSettings | from_json }}"
|
||||
ansible.builtin.set_stats:
|
||||
aggregate: false
|
||||
data:
|
||||
|
||||
Reference in new issue
Block a user