mirror of
https://github.com/bivlked/amneziawg-installer.git
synced 2026-10-14 19:45:02 +00:00
fix(diagnostic): strip key values from the report, not just the private key
The diagnostic report exists to be pasted into a public issue: the bug report
template asks for its contents. It masked exactly one line, `PrivateKey = ...`,
and printed the rest of the server config verbatim. `PresharedKey` lives in that
same file, written there whenever a client is created with `--psk`, so a user who
had enabled the extra layer on top of AWG 2.0 published the preshared key of
every peer, destroying the one thing that layer was there for.
Key values are now stripped by one shared function applied at two points: at the
report boundary and separately to the server config, so that detaching the outer
pipeline cannot silently uncover the riskiest raw input.
Four contexts are covered, each for a reason found by reading upstream
amneziawg-tools rather than guessed:
* configuration lines, anchored, with an optional comment marker, masked to end
of line. Config parsing strips whitespace before parsing (config.c,
config_read_line), so `PrivateKey = AA BB=` is a valid record and cutting at
the first space would have left the tail of the key in the report.
* `awg show` labels. That command hides the private and preshared keys but
prints the header protection key with key(), in clear text (show.c).
* `Line unrecognized: ...`, which echoes the whole line to stderr and therefore
into the journal that the report prints as a section.
* `Key is not the correct length or format: ...`, which carries no key name at
all and cannot be matched by one.
Matching is case insensitive because config parsing is (get_value uses
strncasecmp), and anchored because an unanchored version damaged unrelated
fields: the server name is free text and may itself contain `PrivateKey = ...`.
Public keys, client names and obfuscation parameters are deliberately left in
place: without them the report cannot be used to correlate peers, which is what
it is read for. The report header and the issue template now name both what is
stripped and what is kept; previously both listed only IP addresses, ports and
routes, which invited the reader to assume nothing else was in there.
Verified on a live server: with the pre-fix installer the preshared key value is
present in the report, with this one it is not, while public keys, client names
and the server name survive. A search across all issues, pull requests and
discussions of this repository found no exposed key value.
tests/test_diagnostic_report_secrets.bats covers eleven spellings and the
negative controls; with the fix reverted all thirteen checks fail.
This commit is contained in:
parent
2374aaec80
commit
e305d98b4a
6 changed files with 465 additions and 14 deletions
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -99,8 +99,11 @@ body:
|
|||
description: >-
|
||||
Run `sudo bash install_amneziawg.sh --diagnostic`. The command prints only
|
||||
the PATH to a report file; attach the CONTENTS of that file (default
|
||||
/root/awg/diag_<timestamp>.txt). Mask anything sensitive (public IPs,
|
||||
ports, routes) before pasting.
|
||||
/root/awg/diag_<timestamp>.txt). Recent installer versions strip key
|
||||
values from the report; older ones do not, so check it for `PrivateKey`,
|
||||
`PresharedKey` and `HeaderProtectionKey` lines before pasting. IP
|
||||
addresses, ports, routes, obfuscation parameters, client names and
|
||||
public keys are kept on purpose: redact whatever you do not want public.
|
||||
placeholder: "Contents of /root/awg/diag_<timestamp>.txt"
|
||||
render: text
|
||||
validations:
|
||||
|
|
|
|||
|
|
@ -15,10 +15,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
### Fixed
|
||||
|
||||
- **The swap entry in `/etc/fstab` could be glued onto the preceding line.** `optimize_swap` appended `/swapfile none swap sw 0 0` with `>>` without checking whether the file ended with a newline. When it did not, and some hoster images ship it that way, the entry merged into the last fstab record, and instead of two valid lines the file got a single one of 11 fields instead of six. Worse than the damage itself was how quiet it was: fstab is read on the next boot, so the failure surfaces only after the connection to the server is already gone. The last byte of the file is now checked before writing, and a newline is added when needed. On the stock Ubuntu image the defect did not trigger, because its fstab is properly terminated. Both installer variants fixed
|
||||
- **The diagnostic report printed `PresharedKey` in clear text.** The report is meant to be pasted into a public issue, our own template asks for exactly that, and it masked precisely one line: `PrivateKey`. Yet `PresharedKey` lives in that same server config, written there whenever a client is created with `--psk`. So a user who had enabled the extra layer on top of AWG 2.0 published the preshared keys of every peer, destroying the one thing that layer was there for. Key values are now stripped by one shared function applied at two points: at the report boundary, and separately to the server config as the riskiest raw input. That is not a second source of truth, whereas the previous masking inside a single section did drift apart from the rest, which is how this defect appeared. The filter is case insensitive because config parsing in `amneziawg-tools` is case insensitive too (`config.c`, `get_value` through `strncasecmp`), which makes `presharedkey = ...` a valid line. An exhaustive search across all 168 issues and pull requests and all 55 discussions of this repository found no exposed `PresharedKey` or `PrivateKey` value. That does not amount to "nothing leaked": deleted and edited comments, attachments and anything outside this repository are not covered by such a search
|
||||
- **A key value could reach the report by two more routes, both through the journal.** On an unknown key `awg` prints the line itself to stderr (`Line unrecognized: ...`), already stripped of whitespace and wrapped in a backtick, and on a malformed key it prints `Key is not the correct length or format:`, which carries no key name at all. Both messages land in the journal, which the report prints as a section. Each is now covered by its own filter expression
|
||||
- **A failed filter or a failed write is now fatal.** Previously the failure was logged and execution continued, so the `--diagnostic` branch ended with exit code 0 and the last line on stdout was a cheerful "Report: <path>". The error went to stderr, so anyone redirecting output or checking the exit code saw success only, while the user got an empty file and attached it to an issue. It is now a `die`. Note that this path could never produce an UNFILTERED report: the filter is the only writer of the file
|
||||
- **The report file is created with mode 600 rather than repaired afterwards.** `--diagnostic` runs before the installer narrows the permissions on `/root/awg`, so the window in which the file sat with default permissions was reachable. A `umask 077` is now set for the duration, the same idiom already used for keys
|
||||
- **The server config section now has a fallback line.** If `awg0.conf` cannot be read or filtered, the report states so explicitly. Previously the section was simply empty, which is indistinguishable from an empty config and sent triage in the wrong direction
|
||||
- **A value split by whitespace is masked in full.** Config parsing strips whitespace before parsing (`config.c`, `config_read_line`), so a record like `PrivateKey = AA BB=` is valid. Cutting at the first space would have left the tail of the key in the report, so the value is stripped to end of line
|
||||
- **`awg show` prints `HeaderProtectionKey` in clear text, and it reached the report too.** The private and preshared keys are hidden by `awg show` itself, but the header protection key is printed as is (upstream `src/show.c`: `key()` instead of `masked_key()`). The installer does not configure third-line parameters, but nothing stops anyone from adding them by hand, so the filter covers that spelling as well
|
||||
|
||||
### Changed
|
||||
|
||||
- **The report header and the issue template no longer create a false sense of security.** Both listed only IP addresses, ports and routes as sensitive, from which a reader reasonably concluded that everything else had already been taken care of. Both now name what is stripped (key values) and what is kept on purpose: IP addresses, ports, routes, obfuscation parameters, client names and public keys. Names and public keys are left unmasked deliberately: a public key is not a secret by construction, and without it the report cannot be used to correlate peers, which is what it is read for
|
||||
|
||||
### Added
|
||||
|
||||
- `tests/test_fstab_trailing_newline.bats` (9 checks): static ones proving the guard is present in both installer variants, and functional ones that lift that block **out of the shipped script** and run it against a scratch file. A copy of the logic living in the test would have stayed green after the guard was removed from the installer, so there is none.
|
||||
- `tests/test_diagnostic_report_secrets.bats` (14 checks): static ones proving the filter is present in both variants and that the report really goes through it, and functional ones that lift the filter **out of the shipped installer** and run fourteen spellings through it: plain, lowercase, uppercase without spaces, commented out, a value split by whitespace, an empty value, a CRLF line, `HeaderProtectionKey` in the config, two journal forms (bare and with a `systemctl status` prefix), the malformed-key message, and the three `awg show` forms. Every sample line carries its own marker, so a failure names the exact form. A separate check pins down what is NOT masked: public keys, a client name, a path such as `PrivateKeyFile = /path`, obfuscation parameters, and **a server name, which may itself contain the text `PrivateKey = ...`** - that is where the unanchored version of the filter broke
|
||||
|
||||
## [5.27.1] - 2026-08-23
|
||||
|
||||
|
|
|
|||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -15,10 +15,22 @@
|
|||
### Исправлено
|
||||
|
||||
- **Запись swap в `/etc/fstab` могла приклеиться к предыдущей строке.** `optimize_swap` дописывал `/swapfile none swap sw 0 0` через `>>`, не проверяя, заканчивается ли файл переводом строки. Если не заканчивался, а некоторые образы хостеров приходят именно такими, запись сливалась с последней записью fstab, и вместо двух корректных строк получалась одна из 11 полей вместо шести. Хуже самой порчи её тихость: fstab читается на следующей загрузке, то есть отказ всплывает уже после того, как связь с сервером потеряна. Теперь перед записью проверяется последний байт файла и при необходимости добавляется перевод строки. На стоковом образе Ubuntu дефект не срабатывал: там fstab завершён корректно. Затронуты обе версии установщика
|
||||
- **Диагностический отчёт печатал `PresharedKey` открытым текстом.** Отчёт собирают для вставки в публичный issue, наш же шаблон о том и просит, а маскировалась в нём ровно одна строка, `PrivateKey`. При этом `PresharedKey` физически лежит в том же серверном конфиге: он дописывается туда при создании клиента с `--psk`. То есть пользователь, включивший дополнительный слой поверх AWG 2.0, публиковал общие ключи всех своих пиров и обесценивал ровно тот слой, ради которого его включал. Теперь значения ключей вырезает одна общая функция, применяемая в двух точках: на выходе всего отчёта и отдельно к серверному конфигу как к самому опасному сырому вводу. Двух источников правды это не создаёт, а вот прежняя маскировка внутри одной секции с остальными расходилась - так дефект и возник. Фильтр регистронезависим, потому что разбор конфига в `amneziawg-tools` тоже регистронезависим (`config.c`, `get_value` через `strncasecmp`), то есть `presharedkey = ...` это валидная запись. Сплошной поиск по всем 168 issue и PR и всем 55 обсуждениям репозитория не нашёл ни одного опубликованного значения `PresharedKey` или `PrivateKey`. Утверждать «утечки не было» это не позволяет: удалённые и отредактированные сообщения, вложения и площадки вне репозитория такой поиск не покрывает
|
||||
- **Значение ключа могло уехать в отчёт ещё двумя путями, оба через журнал.** При неизвестном ключе `awg` печатает в stderr саму строку (`Line unrecognized: ...`), уже очищенную от пробелов и обёрнутую в обратный апостроф, а при неверном по формату ключе - сообщение `Key is not the correct length or format:`, в котором имени ключа нет вовсе. Оба сообщения попадают в журнал, который отчёт печатает секцией. Закрыты отдельными выражениями фильтра
|
||||
- **Провал фильтра или записи отчёта теперь фатален.** Раньше при неудаче печаталось сообщение об ошибке, после чего выполнение продолжалось и ветка `--diagnostic` завершалась кодом 0, а последней строкой на stdout шло бодрое «Отчет: <путь>». Ошибка при этом уходила в stderr, то есть при перенаправлении вывода или проверке кода возврата было видно только успех, а пользователь получал пустой файл и вставлял его в issue. Теперь это `die`. ⚠️ Отдать НЕотфильтрованный отчёт этот путь не мог и раньше: единственный, кто пишет в файл, - сам фильтр
|
||||
- **Файл отчёта создаётся сразу с правами 600, а не исправляется после.** `--diagnostic` отрабатывает до того, как установщик сузит права на `/root/awg`, поэтому окно, в котором файл лежал с правами по умолчанию, было достижимо. Теперь на время создания выставляется `umask 077` - тот же приём, что уже применяется к ключам
|
||||
- **У секции серверного конфига появился запасной вывод.** Если прочитать или отфильтровать `awg0.conf` не удалось, в отчёте теперь стоит явная строка об ошибке. Раньше секция оказывалась просто пустой, и это неотличимо от пустого конфига, то есть отчёт уводил разбор не туда
|
||||
- **Значение, разбитое пробелами, маскируется целиком.** Разбор конфига вычищает пробелы до разбора (`config.c`, `config_read_line`), поэтому запись вида `PrivateKey = AA BB=` валидна. Обрезка по первому пробелу оставила бы хвост ключа в отчёте, поэтому значение вырезается до конца строки
|
||||
- **`awg show` печатает `HeaderProtectionKey` открытым текстом, и он тоже попадал в отчёт.** Приватный и общий ключи `awg show` прячет сам, а ключ защиты заголовков выводит как есть (upstream `src/show.c`: `key()` вместо `masked_key()`). Установщик параметры третьей линии не настраивает, но добавлять их вручную никто не запрещает, поэтому фильтр закрывает и эту форму записи
|
||||
|
||||
### Изменено
|
||||
|
||||
- **Шапка отчёта и шаблон issue больше не создают ложного чувства безопасности.** Оба перечисляли как чувствительное только IP-адреса, порты и маршруты, из чего читатель разумно заключал, что с остальным уже разобрались. Теперь оба прямо называют и то, что вырезано (значения ключей), и то, что осталось намеренно: IP-адреса, порты, маршруты, параметры обфускации, имена и публичные ключи клиентов. Имена и публичные ключи не маскируются осознанно: публичный ключ секретом не является по построению, а без него по отчёту нельзя сопоставить пиров, ради чего отчёт и читают
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `tests/test_fstab_trailing_newline.bats` (9 проверок): статические проверки, что гейт присутствует в обеих версиях установщика, и функциональные, которые извлекают этот блок **из поставляемого скрипта** и исполняют его на временном файле. Копия логики внутри теста оставалась бы зелёной после удаления гейта из установщика, поэтому её здесь нет
|
||||
- `tests/test_diagnostic_report_secrets.bats` (14 проверок): статические, что фильтр присутствует в обеих версиях и что отчёт действительно через него проходит, и функциональные, которые извлекают фильтр **из поставляемого установщика** и прогоняют через него четырнадцать форм записи: обычную, в нижнем регистре, в верхнем без пробелов, закомментированную, с разбитым пробелами значением, пустую, с переводом строки CRLF, `HeaderProtectionKey` в конфиге, две журнальные (голую и с префиксом `systemctl status`), сообщение о неверном формате ключа и три формы вывода `awg show`. Каждая строка образца помечена своим маркером, поэтому провал называет конкретную форму. Отдельной проверкой закреплено, что НЕ маскируется: публичные ключи, имя клиента, путь вида `PrivateKeyFile = /path`, параметры обфускации и **имя сервера, которое само может содержать текст `PrivateKey = ...`** - на нём безъякорная редакция фильтра ломалась
|
||||
|
||||
## [5.27.1] - 2026-08-23
|
||||
|
||||
|
|
|
|||
|
|
@ -2221,19 +2221,87 @@ check_service_status() {
|
|||
# Диагностика
|
||||
# ==============================================================================
|
||||
|
||||
# Отчёт собирают для вставки в ПУБЛИЧНЫЙ issue: наш же шаблон бага просит приложить
|
||||
# его содержимое. Значения ключей вырезает одна общая функция, а не отдельная
|
||||
# маскировка в каждой секции: секции со временем добавляются, и разовая маскировка
|
||||
# внутри одной из них расходится с остальными. Именно так PresharedKey и оказался в
|
||||
# отчёте открытым, пока в соседней строке маскировался PrivateKey.
|
||||
# Применяется в ДВУХ точках (одна реализация, не два источника правды): на выходе
|
||||
# всего отчёта и отдельно к серверному конфигу.
|
||||
# ВАЖНО: второй рубеж закрывает ТОЛЬКО серверный конфиг. Вывод awg show и журнал
|
||||
# через него не проходят, так что при отвязанном внешнем конвейере утекут именно они.
|
||||
# ВАЖНО: маскировка AWG_ENDPOINT ниже по функции - осознанное исключение из правила
|
||||
# "одна функция": она прячет адрес, а не ключ, и живёт своей строкой.
|
||||
#
|
||||
# Выражения КОНТЕКСТНЫЕ. Безъякорная редакция резала значение всюду, где встречалось
|
||||
# имя ключа, и портила чужие поля: имя сервера - свободный текст, где разрешены
|
||||
# пробелы и знак равенства, поэтому строка вида AWG_SERVER_NAME с текстом
|
||||
# "PrivateKey = Office" внутри теряла и значение, и закрывающую кавычку. Имена
|
||||
# клиентов от этого защищены валидацией ^[a-zA-Z0-9_-]+$ (она живёт в
|
||||
# manage_amneziawg.sh и awg_common.sh, не здесь), но правленный руками #_Name может
|
||||
# содержать что угодно.
|
||||
#
|
||||
# Четыре контекста:
|
||||
# 1. строка конфигурации В НАЧАЛЕ СТРОКИ, с необязательным символом комментария.
|
||||
# Он нужен НЕ потому, что awg разберёт такую строку: он её как раз отбрасывает
|
||||
# целиком (config_read_line обрезает всё от первой решётки ДО разбора). Нужен
|
||||
# потому, что значение физически лежит в файле, который вставят в публичный
|
||||
# issue. Маскируем ДО КОНЦА СТРОКИ: разбор вычищает пробелы перед разбором,
|
||||
# поэтому запись вида "PrivateKey = AA BB=" валидна, и обрезка по первому
|
||||
# пробелу оставила бы хвост ключа в отчёте.
|
||||
# 2. метка вывода awg show в начале строки. HeaderProtectionKey здесь обязателен:
|
||||
# awg show печатает его ОТКРЫТЫМ ТЕКСТОМ (show.c: key() вместо masked_key()),
|
||||
# в отличие от приватного и общего ключей, которые прячет сам. Заодно снимается
|
||||
# вопрос про WG_HIDE_KEYS=never.
|
||||
# 3. "Line unrecognized: ..." - без якоря. awg печатает в stderr строку УЖЕ
|
||||
# ОЧИЩЕННУЮ (обрезана по решётке, пробелы удалены) и обёрнутую в обратный
|
||||
# апостроф с закрывающей кавычкой. Отсюда ".?" в выражении: он пропускает этот
|
||||
# апостроф. НЕ УДАЛЯТЬ ".?": без него настоящая строка не совпадает вовсе.
|
||||
# Ветка error срабатывает на ЛЮБОЙ неопознанной строке: опечатка в имени ключа,
|
||||
# ключ не в своей секции (PrivateKey внутри [Peer] уедет в stderr целиком),
|
||||
# ключ чужой реализации. Дописанный руками параметр третьей линии - лишь один
|
||||
# из случаев, а не единственный.
|
||||
# 4. "Key is not the correct length or format: ..." - то же место, но имени ключа
|
||||
# в сообщении НЕТ вовсе, поэтому по имени его не поймать.
|
||||
# ВАЖНО: якоря у 1 и 2 означают, что эти формы НЕ ловятся в секции Service Status,
|
||||
# где systemctl status добавляет свой префикс со штампом времени. Журнальная секция
|
||||
# этим не страдает: journalctl вызывается там с --output=cat, то есть без префикса.
|
||||
# Регистронезависимость (флаг I) - разбор конфига тоже регистронезависим (strncasecmp).
|
||||
#
|
||||
# ЧЕТЫРЕ СТРОКОВЫХ ЛИТЕРАЛА UPSTREAM, на которых всё держится: "private key:",
|
||||
# "header protection key:", "Line unrecognized:", "Key is not the correct length or
|
||||
# format:". Сверены с amneziawg-tools ee0f0a9 (src/config.c, src/show.c) 25 aug 2026.
|
||||
# Переформулируют любой - фильтр молча перестанет совпадать, а тесты останутся
|
||||
# зелёными, потому что зашивают те же строки. ПЕРЕСВЕРЯТЬ при обновлении tools.
|
||||
_mask_report_secrets() {
|
||||
sed -E \
|
||||
-e 's/^([[:space:]]*#?[[:space:]]*(PrivateKey|PresharedKey|HeaderProtectionKey)[[:space:]]*=[[:space:]]*).*/\1[HIDDEN]/I' \
|
||||
-e 's/^([[:space:]]*(private key|preshared key|header protection key)[[:space:]]*:[[:space:]]*).*/\1(hidden)/I' \
|
||||
-e 's/(Line unrecognized:[[:space:]]*.?(PrivateKey|PresharedKey|HeaderProtectionKey)[[:space:]]*=[[:space:]]*).*/\1[HIDDEN]/I' \
|
||||
-e 's/(Key is not the correct length or format:[[:space:]]*).*/\1[HIDDEN]/I'
|
||||
}
|
||||
|
||||
create_diagnostic_report() {
|
||||
# --diagnostic вызывается ДО initialize_setup (где живёт основной root-check):
|
||||
# под обычным пользователем запись в /root/awg падает на каждом log_msg,
|
||||
# отчёт не создаётся, а exit 0 выглядел бы как ложный успех.
|
||||
if [ "$(id -u)" -ne 0 ]; then die "Запустите скрипт от root (sudo bash $0 --diagnostic)."; fi
|
||||
log "Создание диагностики..."
|
||||
local rf
|
||||
local rf _diag_umask
|
||||
rf="$AWG_DIR/diag_$(date +%F_%T).txt"
|
||||
# Файл создаётся редиректом ДО chmod, поэтому режим на момент создания задаёт
|
||||
# umask. Тот же приём, что для ключей в awg_common.sh: сузить права сразу, а не
|
||||
# чинить после. --diagnostic отрабатывает ДО secure_files, поэтому /root/awg
|
||||
# может быть создан с правами по умолчанию, и окно 0644 реально достижимо.
|
||||
_diag_umask=$(umask); umask 077
|
||||
{
|
||||
echo "=== AMNEZIAWG 2.0 DIAGNOSTIC REPORT ==="
|
||||
echo ""
|
||||
echo "!!! ВНИМАНИЕ: Отчёт содержит IP-адреса, порты и маршруты."
|
||||
echo "!!! Перед публикацией в issue проверьте и замените приватные данные."
|
||||
echo "!!! ВНИМАНИЕ: значения PrivateKey, PresharedKey и HeaderProtectionKey"
|
||||
echo "!!! вырезаны везде, где они подписаны своим именем или меткой awg show."
|
||||
echo "!!! В отчёте остаются: IP-адреса, порты, маршруты, параметры обфускации,"
|
||||
echo "!!! имена и публичные ключи клиентов. Адрес сервера дополнительно скрыт."
|
||||
echo "!!! Перед публикацией в issue проверьте, что из этого вы не хотите раскрывать."
|
||||
echo ""
|
||||
echo "Generated: $(date)"
|
||||
echo "Hostname: $(hostname)"
|
||||
|
|
@ -2256,9 +2324,11 @@ create_diagnostic_report() {
|
|||
fi
|
||||
echo ""
|
||||
echo "--- Server Config ($SERVER_CONF_FILE) ---"
|
||||
# Маскируем приватный ключ
|
||||
# Второй рубеж ТОЙ ЖЕ функцией: одна реализация, две точки применения.
|
||||
# Дубля правды это не создаёт, а если внешний фильтр когда-нибудь отвяжут
|
||||
# от блока, самый опасный сырой ввод останется прикрытым.
|
||||
if [[ -f "$SERVER_CONF_FILE" ]]; then
|
||||
sed 's/PrivateKey = .*/PrivateKey = [HIDDEN]/' "$SERVER_CONF_FILE"
|
||||
_mask_report_secrets < "$SERVER_CONF_FILE" || echo "ОШИБКА: не удалось прочитать или отфильтровать $SERVER_CONF_FILE"
|
||||
else
|
||||
echo "File not found"
|
||||
fi
|
||||
|
|
@ -2348,7 +2418,8 @@ create_diagnostic_report() {
|
|||
modinfo amneziawg 2>/dev/null || echo "N/A"
|
||||
echo ""
|
||||
echo "=== END ==="
|
||||
} > "$rf" || log_error "Ошибка записи отчета."
|
||||
} | _mask_report_secrets > "$rf" || die "Ошибка записи отчета: $rf"
|
||||
umask "$_diag_umask"
|
||||
chmod 600 "$rf" || log_warn "Ошибка chmod отчета."
|
||||
log "Отчет: $rf"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2239,19 +2239,94 @@ check_service_status() {
|
|||
# Diagnostics
|
||||
# ==============================================================================
|
||||
|
||||
# The report is meant to be pasted into a PUBLIC issue: our own bug template asks
|
||||
# for its contents. Key values are stripped by one shared function rather than by
|
||||
# separate masking inside each section: sections get added over time and masking
|
||||
# done inside one of them drifts apart from the rest. That is exactly how
|
||||
# PresharedKey ended up in the report in clear text while PrivateKey was masked on
|
||||
# the line next to it.
|
||||
# Applied at TWO points (one implementation, not two sources of truth): at the
|
||||
# report boundary and separately to the server config.
|
||||
# NOTE: the second point covers the server config ONLY. awg show output and the
|
||||
# journal do not pass through it, so those are what would leak if the outer
|
||||
# pipeline were ever detached.
|
||||
# NOTE: the AWG_ENDPOINT masking further down this function is a deliberate
|
||||
# exception to the "one function" rule: it hides an address rather than a key.
|
||||
#
|
||||
# The expressions are CONTEXT BOUND. An unanchored version stripped a value wherever
|
||||
# a key name appeared and damaged unrelated fields: the server name is free text
|
||||
# allowing spaces and equals signs, so an AWG_SERVER_NAME line containing the text
|
||||
# "PrivateKey = Office" lost both the value and its closing quote. Client names are
|
||||
# protected from that by the ^[a-zA-Z0-9_-]+$ validation (which lives in
|
||||
# manage_amneziawg.sh and awg_common.sh, not here), but a hand-edited #_Name may
|
||||
# contain anything.
|
||||
#
|
||||
# Four contexts:
|
||||
# 1. a configuration line AT START OF LINE, with an optional comment marker. The
|
||||
# marker is needed NOT because awg would parse such a line: it discards it
|
||||
# entirely (config_read_line truncates at the first hash BEFORE parsing). It is
|
||||
# needed because the value physically sits in a file that gets pasted into a
|
||||
# public issue. Masked TO END OF LINE: parsing strips whitespace beforehand, so
|
||||
# a record like "PrivateKey = AA BB=" is valid and cutting at the first space
|
||||
# would have left the tail of the key in the report.
|
||||
# 2. an awg show label at start of line. HeaderProtectionKey is mandatory here:
|
||||
# awg show prints it IN CLEAR TEXT (show.c: key() instead of masked_key()),
|
||||
# unlike the private and preshared keys which it hides itself. This also
|
||||
# settles WG_HIDE_KEYS=never.
|
||||
# 3. "Line unrecognized: ..." - unanchored. awg prints the line to stderr ALREADY
|
||||
# CLEANED (truncated at the hash, whitespace removed) and wrapped in a backtick
|
||||
# and a quote. Hence the ".?" in the expression: it skips that backtick.
|
||||
# DO NOT REMOVE ".?": without it the real line does not match at all.
|
||||
# The error branch fires on ANY unrecognized line: a typo in a key name, a key
|
||||
# in the wrong section (PrivateKey inside [Peer] goes to stderr in full), a key
|
||||
# from another implementation. A hand-added third-line parameter is one case
|
||||
# among them, not the only one.
|
||||
# 4. "Key is not the correct length or format: ..." - same place, but the message
|
||||
# carries NO key name at all, so it cannot be matched by one.
|
||||
# NOTE: the anchors on 1 and 2 mean those forms are NOT caught in the Service Status
|
||||
# section, where systemctl status adds its own timestamped prefix. The journal
|
||||
# section does not suffer from this: journalctl is called there with --output=cat,
|
||||
# that is, without a prefix.
|
||||
# Case insensitivity (flag I) because config parsing is case insensitive too
|
||||
# (strncasecmp).
|
||||
#
|
||||
# FOUR UPSTREAM STRING LITERALS carry the whole thing: "private key:",
|
||||
# "header protection key:", "Line unrecognized:", "Key is not the correct length or
|
||||
# format:". Checked against amneziawg-tools ee0f0a9 (src/config.c, src/show.c) on
|
||||
# 25 aug 2026. If any of them is reworded upstream the filter silently stops
|
||||
# matching, and the tests stay green because they hard-code the same strings.
|
||||
# RE-CHECK when bumping amneziawg-tools.
|
||||
_mask_report_secrets() {
|
||||
sed -E \
|
||||
-e 's/^([[:space:]]*#?[[:space:]]*(PrivateKey|PresharedKey|HeaderProtectionKey)[[:space:]]*=[[:space:]]*).*/\1[HIDDEN]/I' \
|
||||
-e 's/^([[:space:]]*(private key|preshared key|header protection key)[[:space:]]*:[[:space:]]*).*/\1(hidden)/I' \
|
||||
-e 's/(Line unrecognized:[[:space:]]*.?(PrivateKey|PresharedKey|HeaderProtectionKey)[[:space:]]*=[[:space:]]*).*/\1[HIDDEN]/I' \
|
||||
-e 's/(Key is not the correct length or format:[[:space:]]*).*/\1[HIDDEN]/I'
|
||||
}
|
||||
|
||||
create_diagnostic_report() {
|
||||
# --diagnostic runs BEFORE initialize_setup (home of the main root check):
|
||||
# as a regular user every log_msg write into /root/awg fails, the report
|
||||
# is not created, and exit 0 would look like a false success.
|
||||
if [ "$(id -u)" -ne 0 ]; then die "Run the script as root (sudo bash $0 --diagnostic)."; fi
|
||||
log "Creating diagnostics..."
|
||||
local rf
|
||||
local rf _diag_umask
|
||||
rf="$AWG_DIR/diag_$(date +%F_%T).txt"
|
||||
# The file is created by redirection BEFORE chmod, so its mode at creation
|
||||
# time comes from the umask. Same idiom as for keys in awg_common.sh: narrow
|
||||
# the permissions up front instead of repairing them afterwards. --diagnostic
|
||||
# runs BEFORE secure_files, so /root/awg may exist with default permissions
|
||||
# and the 0644 window is genuinely reachable.
|
||||
_diag_umask=$(umask); umask 077
|
||||
{
|
||||
echo "=== AMNEZIAWG 2.0 DIAGNOSTIC REPORT ==="
|
||||
echo ""
|
||||
echo "!!! WARNING: This report contains IP addresses, ports and routes."
|
||||
echo "!!! Review and redact private data before posting to public issues."
|
||||
echo "!!! WARNING: PrivateKey, PresharedKey and HeaderProtectionKey values are"
|
||||
echo "!!! stripped wherever they are labelled by name or by an awg show label."
|
||||
echo "!!! What stays in the report: IP addresses, ports, routes, obfuscation"
|
||||
echo "!!! parameters, client names and public keys. The server endpoint is"
|
||||
echo "!!! additionally hidden. Review what of that you do not want to be"
|
||||
echo "!!! public before posting to a public issue."
|
||||
echo ""
|
||||
echo "Generated: $(date)"
|
||||
echo "Hostname: $(hostname)"
|
||||
|
|
@ -2274,9 +2349,11 @@ create_diagnostic_report() {
|
|||
fi
|
||||
echo ""
|
||||
echo "--- Server Config ($SERVER_CONF_FILE) ---"
|
||||
# Mask private key
|
||||
# Second enforcement point, SAME function: one implementation, two places.
|
||||
# That is not a second source of truth, and if the outer filter is ever
|
||||
# detached from the block, the riskiest raw input stays covered.
|
||||
if [[ -f "$SERVER_CONF_FILE" ]]; then
|
||||
sed 's/PrivateKey = .*/PrivateKey = [HIDDEN]/' "$SERVER_CONF_FILE"
|
||||
_mask_report_secrets < "$SERVER_CONF_FILE" || echo "ERROR: could not read or filter $SERVER_CONF_FILE"
|
||||
else
|
||||
echo "File not found"
|
||||
fi
|
||||
|
|
@ -2367,7 +2444,8 @@ create_diagnostic_report() {
|
|||
modinfo amneziawg 2>/dev/null || echo "N/A"
|
||||
echo ""
|
||||
echo "=== END ==="
|
||||
} > "$rf" || log_error "Report write error."
|
||||
} | _mask_report_secrets > "$rf" || die "Report write error: $rf"
|
||||
umask "$_diag_umask"
|
||||
chmod 600 "$rf" || log_warn "Report chmod error."
|
||||
log "Report: $rf"
|
||||
}
|
||||
|
|
|
|||
275
tests/test_diagnostic_report_secrets.bats
Normal file
275
tests/test_diagnostic_report_secrets.bats
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#!/usr/bin/env bats
|
||||
# _mask_report_secrets must strip key values from the diagnostic report, and must
|
||||
# not damage anything else in it.
|
||||
#
|
||||
# The report exists to be pasted into a public issue: the bug report template asks
|
||||
# for its contents. Until this guard existed it masked exactly one line,
|
||||
# "PrivateKey = ...", and printed the rest of the server config verbatim, so a user
|
||||
# who had created clients with --psk published the PresharedKey of every peer.
|
||||
#
|
||||
# SCOPE, so the header does not promise more than the file delivers: these checks
|
||||
# exercise the FILTER, lifted out of the shipped installer, plus a textual check
|
||||
# that the report is piped through it. create_diagnostic_report itself is not
|
||||
# executed here (it shells out to awg, journalctl, systemctl, ip, ss, ufw, dkms and
|
||||
# modinfo); it is covered by a live run on a real server instead.
|
||||
#
|
||||
# Every SECRET line in the sample carries its own SEC marker, so a failure names the
|
||||
# exact form that leaked. KEEP markers are the negative controls: an earlier
|
||||
# unanchored version of the filter stripped values wherever a key name appeared,
|
||||
# which destroyed a server name, because that field is free text and may legitimately
|
||||
# contain "PrivateKey = ...".
|
||||
#
|
||||
# The forms come from reading upstream amneziawg-tools, not from caution:
|
||||
# * config parsing is case insensitive (config.c, get_value -> strncasecmp);
|
||||
# * config parsing removes whitespace before parsing (config.c, config_read_line),
|
||||
# so a value split by spaces is a valid record and must be masked to end of line;
|
||||
# * on an unrecognized line awg prints it to stderr ALREADY CLEANED and wrapped in
|
||||
# a backtick and a quote: Line unrecognized: `PrivateKey=VALUE'
|
||||
# The report shows two variants: bare, because the journal section runs
|
||||
# journalctl --output=cat, and timestamped, because systemctl status -l adds its
|
||||
# own prefix;
|
||||
# * a malformed key produces "Key is not the correct length or format: `VALUE'",
|
||||
# which carries NO key name at all;
|
||||
# * awg show hides the private and preshared keys but prints the header protection
|
||||
# key in clear text (show.c: key() instead of masked_key()).
|
||||
|
||||
extract_filter() {
|
||||
awk '/^_mask_report_secrets\(\) \{/,/^\}/' "$1"
|
||||
}
|
||||
|
||||
RU_INSTALL() { echo "$BATS_TEST_DIRNAME/../install_amneziawg.sh"; }
|
||||
EN_INSTALL() { echo "$BATS_TEST_DIRNAME/../install_amneziawg_en.sh"; }
|
||||
TEMPLATE() { echo "$BATS_TEST_DIRNAME/../.github/ISSUE_TEMPLATE/bug_report.yml"; }
|
||||
|
||||
sample_report() {
|
||||
printf '%s\n' \
|
||||
'[Interface]' \
|
||||
'PrivateKey = SEC1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'presharedkey = SEC2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'PRESHAREDKEY=SEC3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'# PresharedKey = SEC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'PrivateKey = SEC5AAAAAAAA SEC5BBBBBBBB SEC5CCCCCCCC==' \
|
||||
'HeaderProtectionKey = SEC6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
"Line unrecognized: \`HeaderProtectionKey=SEC7AAAAAAAAAAAA='" \
|
||||
"Aug 25 07:00:00 h awg-quick[1]: Line unrecognized: \`PresharedKey=SEC12AAAAAAAA='" \
|
||||
"Key is not the correct length or format: \`SEC8AAAAAAAAAAAA='" \
|
||||
' private key: SEC9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
' preshared key: SEC10AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
' header protection key: SEC11AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'PublicKey = KEEP1PUBLICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
' public key: KEEP2PUBLICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' \
|
||||
'#_Name = KEEP3-ivan-iphone' \
|
||||
"export AWG_SERVER_NAME='KEEP4 PrivateKey = Office'" \
|
||||
'PrivateKeyFile = /root/awg/keys/KEEP5.key' \
|
||||
'AllowedIPs = 10.9.9.2/32' \
|
||||
'export AWG_I1=<b 0xKEEP6>' \
|
||||
'PresharedKey = '
|
||||
}
|
||||
|
||||
run_filter() {
|
||||
local block
|
||||
block=$(extract_filter "$1")
|
||||
[ -n "$block" ] || return 1
|
||||
eval "$block"
|
||||
sample_report | _mask_report_secrets
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# static: the filter exists and the report is piped through it
|
||||
# ===========================================================================
|
||||
|
||||
# The body of create_diagnostic_report, so a wiring check cannot be satisfied by a
|
||||
# mention of the pipeline somewhere else in the file.
|
||||
report_fn() {
|
||||
awk '/^create_diagnostic_report\(\) \{/,/^\}/' "$1"
|
||||
}
|
||||
|
||||
# Two mutants motivate this being structural rather than textual, both of which a
|
||||
# plain `grep -qF` accepted while the report went out unfiltered:
|
||||
# A. detach the pipeline and leave the old line in a comment;
|
||||
# B. keep the pipeline and append the raw server config to $rf afterwards.
|
||||
assert_wiring() {
|
||||
local body
|
||||
body=$(report_fn "$1")
|
||||
[ -n "$body" ]
|
||||
# the pipeline must be executable code, not a comment
|
||||
grep -qE '^[[:space:]]*\} \| _mask_report_secrets > "\$rf"' <<< "$body"
|
||||
# and it must be the ONLY writer of the report file
|
||||
[ "$(grep -cE '>>?[[:space:]]*"\$rf"' <<< "$body")" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "diag secrets: RU install defines the filter and pipes the report through it" {
|
||||
grep -q '^_mask_report_secrets() {' "$(RU_INSTALL)"
|
||||
assert_wiring "$(RU_INSTALL)"
|
||||
}
|
||||
|
||||
@test "diag secrets: EN install defines the filter and pipes the report through it" {
|
||||
grep -q '^_mask_report_secrets() {' "$(EN_INSTALL)"
|
||||
assert_wiring "$(EN_INSTALL)"
|
||||
}
|
||||
|
||||
@test "diag secrets: a failed write or filter is fatal, not a logged warning" {
|
||||
# The report is meant to be pasted into a public issue. An empty or truncated
|
||||
# file announced with a success line and exit 0 is worse than a loud failure:
|
||||
# log_error returns success, so without die the branch ends with exit 0.
|
||||
for inst in "$(RU_INSTALL)" "$(EN_INSTALL)"; do
|
||||
body=$(report_fn "$inst")
|
||||
grep -qE '_mask_report_secrets > "\$rf" \|\| die ' <<< "$body"
|
||||
! grep -qE '_mask_report_secrets > "\$rf" \|\| log_error' <<< "$body"
|
||||
done
|
||||
}
|
||||
|
||||
@test "diag secrets: the server config section runs through the same filter" {
|
||||
# Two enforcement points, one implementation: if the outer pipeline is ever
|
||||
# detached from the block, the raw server config stays covered.
|
||||
grep -qF '_mask_report_secrets < "$SERVER_CONF_FILE"' "$(RU_INSTALL)"
|
||||
grep -qF '_mask_report_secrets < "$SERVER_CONF_FILE"' "$(EN_INSTALL)"
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# functional: the shipped filter, executed
|
||||
# ===========================================================================
|
||||
|
||||
@test "diag secrets: RU, every secret form is masked" {
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
# Positive assertions first, same reason as in the EN twin: a test made only of
|
||||
# negatives passes on empty output, and an accidental `sed -n` produces exactly
|
||||
# that with a zero exit status.
|
||||
grep -qx '\[Interface\]' <<< "$out"
|
||||
grep -qx 'PrivateKey = \[HIDDEN\]' <<< "$out"
|
||||
[[ "$out" == *KEEP1PUBLIC* ]]
|
||||
[[ "$out" != *SEC1* ]] # plain config line
|
||||
[[ "$out" != *SEC2* ]] # lowercase key name
|
||||
[[ "$out" != *SEC3* ]] # uppercase, no spaces around =
|
||||
[[ "$out" != *SEC4* ]] # commented out
|
||||
[[ "$out" != *SEC5* ]] # value split by whitespace, still a valid record
|
||||
[[ "$out" != *SEC6* ]] # HeaderProtectionKey in the config
|
||||
[[ "$out" != *SEC7* ]] # journal, bare (journalctl --output=cat)
|
||||
[[ "$out" != *SEC12* ]] # journal, with a systemctl status timestamp
|
||||
[[ "$out" != *SEC8* ]] # journal, malformed key, message carries no key name
|
||||
[[ "$out" != *SEC9* ]] # awg show, clear private key (WG_HIDE_KEYS=never)
|
||||
[[ "$out" != *SEC10* ]] # awg show, preshared key
|
||||
[[ "$out" != *SEC11* ]] # awg show, header protection key
|
||||
}
|
||||
|
||||
@test "diag secrets: EN, every secret form is masked and the KEEP cases survive" {
|
||||
out=$(run_filter "$(EN_INSTALL)")
|
||||
# Positive assertions first: an all-negative test would pass on empty output.
|
||||
grep -qx '\[Interface\]' <<< "$out"
|
||||
grep -qx 'PrivateKey = \[HIDDEN\]' <<< "$out"
|
||||
grep -qx ' preshared key: (hidden)' <<< "$out"
|
||||
[[ "$out" == *KEEP1PUBLIC* ]]
|
||||
grep -qx '#_Name = KEEP3-ivan-iphone' <<< "$out"
|
||||
# The case the whole rewrite exists for, checked in EN too: the two installers
|
||||
# are edited separately, so a divergence would appear exactly here.
|
||||
grep -qxF "export AWG_SERVER_NAME='KEEP4 PrivateKey = Office'" <<< "$out"
|
||||
[[ "$out" != *SEC1* ]]
|
||||
[[ "$out" != *SEC2* ]]
|
||||
[[ "$out" != *SEC3* ]]
|
||||
[[ "$out" != *SEC4* ]]
|
||||
[[ "$out" != *SEC5* ]]
|
||||
[[ "$out" != *SEC6* ]]
|
||||
[[ "$out" != *SEC7* ]]
|
||||
[[ "$out" != *SEC12* ]]
|
||||
[[ "$out" != *SEC8* ]]
|
||||
[[ "$out" != *SEC9* ]]
|
||||
[[ "$out" != *SEC10* ]]
|
||||
[[ "$out" != *SEC11* ]]
|
||||
}
|
||||
|
||||
@test "diag secrets: not one SEC marker of any kind is left" {
|
||||
# Backstop: if a form is added to the sample and its own assertion is
|
||||
# forgotten, this one still fails.
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
! grep -q SEC <<< "$out"
|
||||
}
|
||||
|
||||
@test "diag secrets: config-file spelling keeps its key name and marker" {
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
grep -qx 'PrivateKey = \[HIDDEN\]' <<< "$out"
|
||||
grep -qx 'presharedkey = \[HIDDEN\]' <<< "$out"
|
||||
grep -qx 'PRESHAREDKEY=\[HIDDEN\]' <<< "$out"
|
||||
grep -qx 'HeaderProtectionKey = \[HIDDEN\]' <<< "$out"
|
||||
# An empty value must still be marked, not silently left alone.
|
||||
grep -qx 'PresharedKey = \[HIDDEN\]' <<< "$out"
|
||||
}
|
||||
|
||||
@test "diag secrets: awg show spelling keeps its label and marker" {
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
# The sample feeds CLEAR values here, not already hidden ones: otherwise
|
||||
# these checks would pass with no filter at all.
|
||||
grep -qx ' private key: (hidden)' <<< "$out"
|
||||
grep -qx ' preshared key: (hidden)' <<< "$out"
|
||||
grep -qx ' header protection key: (hidden)' <<< "$out"
|
||||
}
|
||||
|
||||
@test "diag secrets: journal lines in the real upstream format are masked" {
|
||||
# The exact shape matters: upstream wraps the cleaned line in a backtick and a
|
||||
# quote. A fixture without the backtick would leave the one regex character
|
||||
# that exists for it untested, and removing that character would still keep
|
||||
# every check green while the real line leaked in full.
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
grep -q 'Line unrecognized: `HeaderProtectionKey=\[HIDDEN\]' <<< "$out"
|
||||
grep -q 'Line unrecognized: `PresharedKey=\[HIDDEN\]' <<< "$out"
|
||||
grep -q 'Key is not the correct length or format: \[HIDDEN\]' <<< "$out"
|
||||
# The timestamped variant must keep its prefix readable.
|
||||
grep -q 'awg-quick\[1\]: Line unrecognized:' <<< "$out"
|
||||
}
|
||||
|
||||
@test "diag secrets: everything that is not a key survives untouched" {
|
||||
out=$(run_filter "$(RU_INSTALL)")
|
||||
# A public key is not a secret and peers cannot be correlated without it.
|
||||
[[ "$out" == *KEEP1PUBLIC* ]]
|
||||
[[ "$out" == *KEEP2PUBLIC* ]]
|
||||
grep -qx '#_Name = KEEP3-ivan-iphone' <<< "$out"
|
||||
# A server name is FREE TEXT and may contain "PrivateKey = ...". An earlier
|
||||
# unanchored filter ate this line together with its closing quote.
|
||||
grep -qxF "export AWG_SERVER_NAME='KEEP4 PrivateKey = Office'" <<< "$out"
|
||||
# A path is not a key: the pattern requires = right after the key name.
|
||||
grep -qx 'PrivateKeyFile = /root/awg/keys/KEEP5.key' <<< "$out"
|
||||
grep -qx 'AllowedIPs = 10.9.9.2/32' <<< "$out"
|
||||
[[ "$out" == *KEEP6* ]]
|
||||
}
|
||||
|
||||
@test "diag secrets: filtering an already filtered report does not double the markers" {
|
||||
# Plain "once == twice" is satisfied by a filter that matches nothing, so it
|
||||
# proves little on its own. Assert the property that actually matters: the
|
||||
# markers already present are not wrapped again.
|
||||
block=$(extract_filter "$(RU_INSTALL)")
|
||||
eval "$block"
|
||||
once=$(sample_report | _mask_report_secrets)
|
||||
twice=$(sample_report | _mask_report_secrets | _mask_report_secrets)
|
||||
[ "$once" = "$twice" ]
|
||||
[[ "$once" == *"[HIDDEN]"* ]]
|
||||
[[ "$once" == *"(hidden)"* ]]
|
||||
[[ "$twice" != *"[HIDDEN][HIDDEN]"* ]]
|
||||
[[ "$twice" != *"(hidden)(hidden)"* ]]
|
||||
}
|
||||
|
||||
@test "diag secrets: a CRLF line is masked in both installers" {
|
||||
for inst in "$(RU_INSTALL)" "$(EN_INSTALL)"; do
|
||||
block=$(extract_filter "$inst")
|
||||
eval "$block"
|
||||
out=$(printf 'PrivateKey = SECCRLFAAAAAAAAAAAA=\r\nPublicKey = KEEPCRLF=\r\n' | _mask_report_secrets)
|
||||
[[ "$out" != *SECCRLF* ]]
|
||||
[[ "$out" == *KEEPCRLF* ]]
|
||||
done
|
||||
}
|
||||
|
||||
@test "diag secrets: the report warning and the issue template both name the keys" {
|
||||
# The old warning listed only IPs, ports and routes, so a reader concluded
|
||||
# nothing else was in there. The issue template had the same gap: it asked to
|
||||
# mask "public IPs, ports, routes" and never mentioned keys at all.
|
||||
# The guarantee must be NARROW: the mechanism recognises a key by its name or
|
||||
# by an awg show label, so an unlabelled value (a future `awg show all dump`
|
||||
# section, say) would not be covered and an unconditional promise would lie.
|
||||
grep -qF 'PresharedKey и HeaderProtectionKey' "$(RU_INSTALL)"
|
||||
grep -qF 'подписаны своим именем или меткой awg show' "$(RU_INSTALL)"
|
||||
grep -qF 'PresharedKey and HeaderProtectionKey' "$(EN_INSTALL)"
|
||||
grep -qF 'labelled by name or by an awg show label' "$(EN_INSTALL)"
|
||||
grep -qF 'PresharedKey' "$(TEMPLATE)"
|
||||
grep -qF 'HeaderProtectionKey' "$(TEMPLATE)"
|
||||
# and it must still name what is deliberately kept
|
||||
grep -qF 'client names' "$(TEMPLATE)"
|
||||
grep -qF 'public keys' "$(TEMPLATE)"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue