feat(backup): add audited backup listing and restoration
This commit is contained in:
+311
-20
@@ -54,7 +54,7 @@ print_usage() {
|
||||
Использование:
|
||||
wg-manager.sh help
|
||||
wg-manager.sh version
|
||||
wg-manager.sh {status|list|add NAME [--mode MODE] [--endpoint HOST]|show NAME|config NAME [--show]|qr NAME|remove NAME [--yes]|diagnose [--quiet]|logs|backups|restore|tui}
|
||||
wg-manager.sh {status|list|add NAME [--mode MODE] [--endpoint HOST]|show NAME|config NAME [--show]|qr NAME|remove NAME [--yes]|diagnose [--quiet]|backup|backups|restore BACKUP [--yes]|log [LIMIT]|tui}
|
||||
|
||||
Команды управления WireGuard будут добавлены в следующих версиях.
|
||||
USAGE
|
||||
@@ -577,6 +577,7 @@ TRANSACTION_PENDING_SIGNAL=''
|
||||
TRANSACTION_ROLLBACK_ACTIVE=0
|
||||
TRANSACTION_ROLLBACK_INTERRUPTED=0
|
||||
TRANSACTION_ROLLBACK_SIGNAL=''
|
||||
TRANSACTION_OUTCOME='failed-before-apply'
|
||||
|
||||
transaction_test_hooks_enabled() {
|
||||
local path config_path
|
||||
@@ -727,10 +728,12 @@ create_backup() {
|
||||
}
|
||||
|
||||
validate_backup_manifest() {
|
||||
local backup=$1 line key value manifest_interface='' manifest_timestamp='' manifest_operation='' manifest_target='' manifest_config='' metadata_name backup_name backup_prefix backup_counter
|
||||
local backup=$1 line key value manifest_interface='' manifest_timestamp='' manifest_operation='' manifest_target='' manifest_config='' metadata_name backup_name backup_prefix backup_counter server_owner
|
||||
local -A seen=() metadata_seen=()
|
||||
[[ -d "$backup" && ! -L "$backup" && -f "$backup/manifest" && ! -L "$backup/manifest" && -f "$backup/wg.conf" && ! -L "$backup/wg.conf" ]] || return 1
|
||||
[[ $(stat -c '%a' -- "$backup") == 700 && $(stat -c '%a' -- "$backup/manifest") == 600 && $(stat -c '%a' -- "$backup/wg.conf") == 600 ]] || return 1
|
||||
[[ $backup == "${BACKUP_DIR%/}"/* && ${backup%/*} == "${BACKUP_DIR%/}" ]] || return 1
|
||||
server_owner=$(stat -c '%u:%g' -- "$WG_CONF") || return 1
|
||||
[[ $(stat -c '%a:%u:%g' -- "$backup") == "700:$server_owner" && $(stat -c '%a:%h:%u:%g' -- "$backup/manifest") == "600:1:$server_owner" && $(stat -c '%a:%h:%u:%g' -- "$backup/wg.conf") == "600:1:$server_owner" ]] || return 1
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ $line == *=* ]] || return 1
|
||||
key=${line%%=*}; value=${line#*=}
|
||||
@@ -752,14 +755,14 @@ validate_backup_manifest() {
|
||||
esac
|
||||
done <"$backup/manifest"
|
||||
[[ ${seen[INTERFACE]+x} && ${seen[TIMESTAMP]+x} && ${seen[OPERATION]+x} && ${seen[TARGET]+x} && ${seen[SOURCE_MODE]+x} && ${seen[SOURCE_UID]+x} && ${seen[SOURCE_GID]+x} && ${seen[CONFIG]+x} ]] || return 1
|
||||
[[ $manifest_interface == "$WG_INTERFACE" && $manifest_timestamp =~ ^[0-9]{8}T[0-9]{6}Z$ && $manifest_operation =~ ^[A-Za-z][A-Za-z0-9._:-]{0,63}$ && $manifest_target =~ ^[A-Za-z][A-Za-z0-9._:/-]{0,127}$ && $manifest_config == wg.conf ]] || return 1
|
||||
[[ $manifest_interface == "$WG_INTERFACE" && $manifest_timestamp =~ ^[0-9]{8}T[0-9]{6}Z$ && $manifest_operation =~ ^[A-Za-z][A-Za-z0-9._:-]{0,63}$ && $manifest_target =~ ^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$ && $manifest_config == wg.conf ]] || return 1
|
||||
backup_name=${backup##*/}
|
||||
backup_prefix="${manifest_timestamp}-${WG_INTERFACE}-"
|
||||
[[ $backup_name == "$backup_prefix"* ]] || return 1
|
||||
backup_counter=${backup_name#"$backup_prefix"}
|
||||
[[ $backup_counter =~ ^[0-9]{3}$ ]] || return 1
|
||||
for metadata_name in "${!metadata_seen[@]}"; do
|
||||
[[ -f "$backup/$metadata_name" && ! -L "$backup/$metadata_name" && $(stat -c '%a' -- "$backup/$metadata_name") == 600 ]] || return 1
|
||||
[[ -f "$backup/$metadata_name" && ! -L "$backup/$metadata_name" && $(stat -c '%a:%h:%u:%g' -- "$backup/$metadata_name") == "600:1:$server_owner" ]] || return 1
|
||||
done
|
||||
for line in "$backup"/*; do
|
||||
[[ -e "$line" || -L "$line" ]] || continue
|
||||
@@ -833,7 +836,7 @@ transaction_subshell_exit() {
|
||||
local status=$?
|
||||
transaction_cleanup
|
||||
if [[ -n ${TRANSACTION_RESULT_FILE:-} ]]; then
|
||||
printf 'STATUS=%s\nBACKUP=%s\n' "$status" "${TRANSACTION_BACKUP_PATH:-}" >"$TRANSACTION_RESULT_FILE"
|
||||
printf 'STATUS=%s\nBACKUP=%s\nOUTCOME=%s\n' "$status" "${TRANSACTION_BACKUP_PATH:-}" "${TRANSACTION_OUTCOME:-failed-before-apply}" >"$TRANSACTION_RESULT_FILE"
|
||||
if ! chmod 600 -- "$TRANSACTION_RESULT_FILE"; then
|
||||
printf 'Предупреждение: не удалось защитить transaction result\n' >&2
|
||||
fi
|
||||
@@ -846,9 +849,9 @@ run_transaction_core() {
|
||||
local source_mode source_uid source_gid backup_path finalizer_status=0
|
||||
local transaction_path
|
||||
shift 3
|
||||
TRANSACTION_TEMP_PATHS=(); TRANSACTION_BACKUP_PATH=''
|
||||
TRANSACTION_TEMP_PATHS=(); TRANSACTION_BACKUP_PATH=''; TRANSACTION_OUTCOME='failed-before-apply'
|
||||
TRANSACTION_OWNED_PATHS=(); TRANSACTION_PENDING_SIGNAL=''; TRANSACTION_ROLLBACK_ACTIVE=0; TRANSACTION_ROLLBACK_INTERRUPTED=0; TRANSACTION_ROLLBACK_SIGNAL=''
|
||||
[[ $operation =~ ^[A-Za-z][A-Za-z0-9._:-]{0,63}$ && $target =~ ^[A-Za-z][A-Za-z0-9._:/-]{0,127}$ ]] || { die "$EXIT_VALIDATION" 'Некорректная операция или цель транзакции'; return $?; }
|
||||
[[ $operation =~ ^[A-Za-z][A-Za-z0-9._:-]{0,63}$ && $target =~ ^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$ ]] || { die "$EXIT_VALIDATION" 'Некорректная операция или цель транзакции'; return $?; }
|
||||
is_control_free "$operation" && is_control_free "$target" || { die "$EXIT_VALIDATION" 'Некорректная операция или цель транзакции'; return $?; }
|
||||
[[ $builder =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || { die "$EXIT_VALIDATION" 'Некорректный builder транзакции'; return $?; }
|
||||
declare -F "$builder" >/dev/null 2>&1 || { die "$EXIT_VALIDATION" 'Builder транзакции не является функцией'; return $?; }
|
||||
@@ -906,12 +909,18 @@ run_transaction_core() {
|
||||
if ((status == 0)); then
|
||||
backup_path=$(create_backup "$WG_CONF" "$operation" "$target" "$@") || status=$?
|
||||
TRANSACTION_BACKUP_PATH=${backup_path:-}
|
||||
if [[ $status == 0 ]]; then
|
||||
if ! write_audit_event backup "$target" success success >/dev/null 2>&1; then warn 'Не удалось записать аудит резервной копии'; fi
|
||||
else
|
||||
if ! write_audit_event backup "$target" failed backup-failed >/dev/null 2>&1; then warn 'Не удалось записать аудит ошибки резервной копии'; fi
|
||||
fi
|
||||
if [[ $status == 0 ]]; then
|
||||
if [[ ${TRANSACTION_ATOMIC_REPLACE_FAIL:-0} == 1 ]] && transaction_test_hooks_enabled; then status=$EXIT_OPERATION
|
||||
elif ! chmod 600 -- "$stage" || ! mv -- "$stage" "$WG_CONF"; then status=$EXIT_OPERATION; fi
|
||||
fi
|
||||
fi
|
||||
if ((status == 0)); then
|
||||
TRANSACTION_OUTCOME='apply-attempted'
|
||||
restore_file_metadata "$WG_CONF" "$source_mode" "$source_uid" "$source_gid" || status=$EXIT_OPERATION
|
||||
fi
|
||||
if ((status == 0)) && ! run_syncconf "$WG_INTERFACE" "$new_stripped"; then status=$EXIT_OPERATION; fi
|
||||
@@ -956,9 +965,11 @@ run_transaction_core() {
|
||||
printf 'КРИТИЧНО: откат прерван сигналом %s\n' "$TRANSACTION_ROLLBACK_SIGNAL" >&2
|
||||
status=$EXIT_ROLLBACK
|
||||
else
|
||||
if (( finalizer_status == EXIT_ROLLBACK )); then status=$EXIT_ROLLBACK; else status=$EXIT_OPERATION; fi
|
||||
if (( finalizer_status == EXIT_ROLLBACK )); then status=$EXIT_ROLLBACK; else status=$EXIT_OPERATION; fi
|
||||
TRANSACTION_OUTCOME='rolled-back'
|
||||
fi
|
||||
fi
|
||||
if ((status == EXIT_ROLLBACK)); then TRANSACTION_OUTCOME='rollback-failed'; fi
|
||||
TRANSACTION_ROLLBACK_ACTIVE=0
|
||||
fi
|
||||
if (( status == 0 )) && [[ -n ${backup_path:-} ]]; then
|
||||
@@ -966,6 +977,7 @@ run_transaction_core() {
|
||||
printf 'Предупреждение: не удалось ограничить число резервных копий\n' >&2
|
||||
fi
|
||||
fi
|
||||
(( status == 0 )) && TRANSACTION_OUTCOME='success'
|
||||
transaction_cleanup; exec {lock_fd}>&-; return "$status"
|
||||
}
|
||||
|
||||
@@ -974,7 +986,7 @@ run_transaction() {
|
||||
die "$EXIT_USAGE" 'Ожидаются operation, target и builder транзакции'
|
||||
return $?
|
||||
fi
|
||||
local operation=$1 target=$2 builder=$3 result_file result_status result_backup had_errexit=0
|
||||
local operation=$1 target=$2 builder=$3 result_file result_status result_backup result_outcome had_errexit=0
|
||||
shift 3
|
||||
[[ -n ${WG_MANAGER_CONFIG:-} ]] || { die "$EXIT_VALIDATION" 'Не задан manager config'; return $?; }
|
||||
set_runtime_paths || return $?
|
||||
@@ -993,11 +1005,21 @@ run_transaction() {
|
||||
(( had_errexit == 1 )) && set -e || set +e
|
||||
if [[ -r $result_file ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
case $key in STATUS) result_status=$value ;; BACKUP) result_backup=$value ;; esac
|
||||
case $key in STATUS) result_status=$value ;; BACKUP) result_backup=$value ;; OUTCOME) result_outcome=$value ;; esac
|
||||
done <"$result_file"
|
||||
fi
|
||||
rm -f -- "$result_file"
|
||||
TRANSACTION_BACKUP_PATH=${result_backup:-}
|
||||
TRANSACTION_OUTCOME=${result_outcome:-failed-before-apply}
|
||||
if [[ $result_outcome == success ]]; then
|
||||
if ! write_audit_event apply "$target" success success >/dev/null 2>&1; then warn 'Не удалось записать аудит применения'; fi
|
||||
elif [[ $result_outcome == rolled-back ]]; then
|
||||
if ! write_audit_event rollback "$target" success rolled-back >/dev/null 2>&1; then warn 'Не удалось записать аудит отката'; fi
|
||||
elif [[ $result_outcome == rollback-failed ]]; then
|
||||
if ! write_audit_event rollback "$target" failed rollback-failed >/dev/null 2>&1; then warn 'Не удалось записать аудит критического отката'; fi
|
||||
elif [[ $result_outcome == failed-before-apply ]]; then
|
||||
if ! write_audit_event transaction "$target" failed operation-failed >/dev/null 2>&1; then warn 'Не удалось записать аудит неуспешной транзакции'; fi
|
||||
fi
|
||||
unset TRANSACTION_RESULT_FILE TRANSACTION_PRE_LOCK_FILE TRANSACTION_PRE_WG_CONF TRANSACTION_PRE_BACKUP_DIR TRANSACTION_PRE_METADATA_DIR TRANSACTION_PRE_INTERFACE TRANSACTION_FINALIZER
|
||||
return "$result_status"
|
||||
}
|
||||
@@ -1354,10 +1376,16 @@ add_client_impl() {
|
||||
status=$?
|
||||
fi
|
||||
if (( status != 0 )); then
|
||||
if ! AUDIT_CLIENT=$name AUDIT_KEY_SUFFIX='' write_audit_event add "$name" failed transaction_failed >/dev/null 2>&1; then warn 'Не удалось записать аудит добавления'; fi
|
||||
clear_add_transaction_state
|
||||
return "$status"
|
||||
fi
|
||||
status=0
|
||||
if ! AUDIT_CLIENT=$name AUDIT_KEY_SUFFIX='' write_audit_event add "$name" success success >/dev/null 2>&1; then
|
||||
clear_add_transaction_state
|
||||
die "$EXIT_OPERATION" 'Добавление выполнено, но аудит не записан (частичный результат)'
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
clear_add_transaction_state
|
||||
return "$status"
|
||||
}
|
||||
@@ -1407,6 +1435,244 @@ resolve_gum() {
|
||||
|
||||
print_version() { printf 'WireGuard Manager %s\n' "$WG_MANAGER_VERSION"; }
|
||||
run_placeholder() { load_config || return $?; die "$EXIT_OPERATION" "Команда «$1» пока не реализована"; }
|
||||
manual_backup() {
|
||||
local lock_fd path status parent pre_conf pre_backup pre_meta pre_lock pre_iface live_owner lock_owner lock_created=0 lock_identity opened_identity replacement_backup config_identity config_identity_after backup_identity_after
|
||||
load_config || return $?
|
||||
require_add_privilege || return $?
|
||||
path_components_safe "$LOCK_FILE" || { die "$EXIT_VALIDATION" 'Некорректный путь блокировки'; return $?; }
|
||||
path_components_safe "$WG_CONF" || { die "$EXIT_VALIDATION" 'Некорректный путь конфигурации'; return $?; }
|
||||
parent=${LOCK_FILE%/*}; [[ -d $parent && ! -L $parent ]] || { die "$EXIT_VALIDATION" 'Каталог блокировки недоступен'; return $?; }
|
||||
pre_conf=$WG_CONF; pre_backup=$BACKUP_DIR; pre_meta=$METADATA_DIR; pre_lock=$LOCK_FILE; pre_iface=$WG_INTERFACE
|
||||
if [[ -e $LOCK_FILE ]]; then
|
||||
[[ -f $LOCK_FILE && ! -L $LOCK_FILE && $(stat -c '%a:%h' -- "$LOCK_FILE") == 600:1 ]] || { die "$EXIT_VALIDATION" 'Файл блокировки небезопасен'; return $?; }
|
||||
else
|
||||
(umask 077; : >"$LOCK_FILE") || { die "$EXIT_OPERATION" 'Не удалось создать файл блокировки'; return $?; }
|
||||
chmod 600 -- "$LOCK_FILE" || { rm -f -- "$LOCK_FILE"; die "$EXIT_OPERATION" 'Не удалось защитить блокировку'; return $?; }
|
||||
lock_created=1
|
||||
fi
|
||||
lock_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$LOCK_FILE") || { ((lock_created == 1)) && rm -f -- "$LOCK_FILE"; die "$EXIT_OPERATION" 'Не удалось проверить блокировку'; return $?; }
|
||||
if [[ -n ${MANUAL_BACKUP_PREOPEN_REPLACEMENT:-} ]]; then
|
||||
if ! transaction_test_hooks_enabled || ! path_components_safe "$MANUAL_BACKUP_PREOPEN_REPLACEMENT" || [[ ! -f $MANUAL_BACKUP_PREOPEN_REPLACEMENT || -L $MANUAL_BACKUP_PREOPEN_REPLACEMENT || $MANUAL_BACKUP_PREOPEN_REPLACEMENT == "$LOCK_FILE" ]]; then
|
||||
((lock_created == 1)) && rm -f -- "$LOCK_FILE"
|
||||
die "$EXIT_VALIDATION" 'Некорректный тестовый файл блокировки'
|
||||
return $?
|
||||
fi
|
||||
replacement_backup=${LOCK_FILE}.manual-preopen-old
|
||||
rm -f -- "$replacement_backup"
|
||||
mv -- "$LOCK_FILE" "$replacement_backup" || return "$EXIT_OPERATION"
|
||||
cp -- "$MANUAL_BACKUP_PREOPEN_REPLACEMENT" "$LOCK_FILE" || { mv -- "$replacement_backup" "$LOCK_FILE"; die "$EXIT_OPERATION" 'Не удалось подготовить тестовую блокировку'; return $?; }
|
||||
fi
|
||||
exec {lock_fd}<>"$LOCK_FILE" || { ((lock_created == 1)) && rm -f -- "$LOCK_FILE"; die "$EXIT_OPERATION" 'Не удалось открыть блокировку'; return $?; }
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${lock_fd}") || { exec {lock_fd}>&-; die "$EXIT_OPERATION" 'Не удалось проверить открытую блокировку'; return $?; }
|
||||
if [[ $opened_identity != "$lock_identity" ]]; then
|
||||
exec {lock_fd}>&-
|
||||
if [[ -n ${replacement_backup:-} && -e $replacement_backup ]]; then rm -f -- "$LOCK_FILE"; mv -- "$replacement_backup" "$LOCK_FILE"; fi
|
||||
die "$EXIT_VALIDATION" 'Файл блокировки изменился перед открытием'
|
||||
return $?
|
||||
fi
|
||||
flock -n "$lock_fd" || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$WG_INTERFACE" failed lock-contended >/dev/null 2>&1; then warn 'Не удалось записать аудит блокировки'; fi; die "$EXIT_OPERATION" 'Другая операция уже выполняется'; return $?; }
|
||||
load_config || { status=$?; exec {lock_fd}>&-; return "$status"; }
|
||||
if [[ $WG_CONF != "$pre_conf" || $BACKUP_DIR != "$pre_backup" || $METADATA_DIR != "$pre_meta" || $LOCK_FILE != "$pre_lock" || $WG_INTERFACE != "$pre_iface" ]]; then
|
||||
exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed resources-changed >/dev/null 2>&1; then warn 'Не удалось записать аудит изменения ресурсов'; fi; die "$EXIT_VALIDATION" 'Ресурсы изменились после блокировки'; return $?
|
||||
fi
|
||||
[[ -f $WG_CONF && ! -L $WG_CONF ]] || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-unavailable >/dev/null 2>&1; then warn 'Не удалось записать аудит конфигурации'; fi; die "$EXIT_VALIDATION" 'Серверная конфигурация недоступна'; return $?; }
|
||||
live_owner=$(stat -c '%u:%g' -- "$WG_CONF") || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-stat >/dev/null 2>&1; then warn 'Не удалось записать аудит конфигурации'; fi; return "$EXIT_OPERATION"; }
|
||||
[[ $(stat -c '%a:%h' -- "$WG_CONF") == 400:1 || $(stat -c '%a:%h' -- "$WG_CONF") == 600:1 ]] || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-permissions >/dev/null 2>&1; then warn 'Не удалось записать аудит прав'; fi; die "$EXIT_VALIDATION" 'Небезопасные права серверной конфигурации'; return $?; }
|
||||
[[ $(stat -c '%u:%g' -- "$WG_CONF") == "$live_owner" ]] || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-owner >/dev/null 2>&1; then warn 'Не удалось записать аудит владельца'; fi; die "$EXIT_VALIDATION" 'Недоверенный владелец серверной конфигурации'; return $?; }
|
||||
lock_owner=$(stat -c '%u:%g' -- "$LOCK_FILE") || { exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
[[ $lock_owner == "$live_owner" ]] || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed lock-owner >/dev/null 2>&1; then warn 'Не удалось записать аудит владельца блокировки'; fi; die "$EXIT_VALIDATION" 'Недоверенный владелец блокировки'; return $?; }
|
||||
parse_wireguard_config "$WG_CONF" >/dev/null 2>&1 || { exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-parse >/dev/null 2>&1; then warn 'Не удалось записать аудит конфигурации'; fi; die "$EXIT_VALIDATION" 'Серверная конфигурация не прошла проверку'; return $?; }
|
||||
config_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$WG_CONF") || { exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
if [[ -n ${MANUAL_BACKUP_PRECOPY_REPLACEMENT:-} ]]; then
|
||||
if ! transaction_test_hooks_enabled || ! path_components_safe "$MANUAL_BACKUP_PRECOPY_REPLACEMENT" || [[ ! -f $MANUAL_BACKUP_PRECOPY_REPLACEMENT || -L $MANUAL_BACKUP_PRECOPY_REPLACEMENT || $MANUAL_BACKUP_PRECOPY_REPLACEMENT == "$WG_CONF" ]]; then
|
||||
exec {lock_fd}>&-; die "$EXIT_VALIDATION" 'Некорректный тестовый источник конфигурации'; return $?
|
||||
fi
|
||||
replacement_backup=${WG_CONF}.manual-precopy-old
|
||||
rm -f -- "$replacement_backup"
|
||||
mv -- "$WG_CONF" "$replacement_backup" || { exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
cp -- "$MANUAL_BACKUP_PRECOPY_REPLACEMENT" "$WG_CONF" || { mv -- "$replacement_backup" "$WG_CONF"; exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
fi
|
||||
config_identity_after=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$WG_CONF") || { exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
if [[ $config_identity_after != "$config_identity" ]]; then
|
||||
[[ -n ${replacement_backup:-} && -e $replacement_backup ]] && { rm -f -- "$WG_CONF"; mv -- "$replacement_backup" "$WG_CONF"; }
|
||||
exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-changed >/dev/null 2>&1; then warn 'Не удалось записать аудит изменения конфигурации'; fi; die "$EXIT_VALIDATION" 'Конфигурация изменилась перед резервированием'; return $?
|
||||
fi
|
||||
path=$(create_backup "$WG_CONF" manual "$WG_INTERFACE") || { status=$?; exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed backup-failed >/dev/null 2>&1; then warn 'Не удалось записать аудит резервной копии'; fi; return "$status"; }
|
||||
backup_identity_after=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$WG_CONF") || { rm -rf -- "$path"; exec {lock_fd}>&-; return "$EXIT_OPERATION"; }
|
||||
if [[ $backup_identity_after != "$config_identity" ]]; then
|
||||
rm -rf -- "$path"
|
||||
[[ -n ${replacement_backup:-} && -e $replacement_backup ]] && { rm -f -- "$WG_CONF"; mv -- "$replacement_backup" "$WG_CONF"; }
|
||||
exec {lock_fd}>&-; if ! write_audit_event manual-backup "$pre_iface" failed config-changed >/dev/null 2>&1; then warn 'Не удалось записать аудит изменения конфигурации'; fi; die "$EXIT_VALIDATION" 'Конфигурация изменилась во время резервирования'; return $?
|
||||
fi
|
||||
if ! prune_backups; then warn 'Не удалось ограничить число резервных копий'; fi
|
||||
exec {lock_fd}>&-
|
||||
if ! AUDIT_OPERATION=manual-backup AUDIT_TARGET="$WG_INTERFACE" AUDIT_ERROR_CATEGORY=success write_audit_event manual-backup "$WG_INTERFACE" success success; then
|
||||
die "$EXIT_OPERATION" 'Резервная копия создана, но аудит не записан (частичный результат)'
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
list_backups() {
|
||||
local path name line timestamp operation target value invalid_count=0 root_mode root_owner server_owner manifest_identity_before manifest_identity_after manifest_fd manifest_opened_identity
|
||||
local -a entries=()
|
||||
load_config || return $?
|
||||
if [[ ! -e $BACKUP_DIR ]]; then printf 'Резервные копии отсутствуют\n'; return 0; fi
|
||||
[[ -d $BACKUP_DIR && ! -L $BACKUP_DIR ]] && path_components_safe "$BACKUP_DIR" || { die "$EXIT_VALIDATION" 'Каталог резервных копий небезопасен'; return $?; }
|
||||
server_owner=$(stat -c '%u:%g' -- "$WG_CONF") || return "$EXIT_OPERATION"
|
||||
root_mode=$(stat -c '%a' -- "$BACKUP_DIR") || return "$EXIT_OPERATION"
|
||||
root_owner=$(stat -c '%u:%g' -- "$BACKUP_DIR") || return "$EXIT_OPERATION"
|
||||
[[ $root_mode == 700 && $root_owner == "$server_owner" ]] || { die "$EXIT_VALIDATION" 'Каталог резервных копий небезопасен'; return $?; }
|
||||
for path in "$BACKUP_DIR"/*; do
|
||||
[[ -d $path && ! -L $path ]] || { [[ -e $path ]] && ((invalid_count += 1)); continue; }
|
||||
manifest_identity_before=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$path/manifest" 2>/dev/null) || { ((invalid_count += 1)); continue; }
|
||||
exec {manifest_fd}<"$path/manifest" || { ((invalid_count += 1)); continue; }
|
||||
manifest_opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${manifest_fd}" 2>/dev/null) || { exec {manifest_fd}>&-; ((invalid_count += 1)); continue; }
|
||||
if [[ $manifest_opened_identity != "$manifest_identity_before" ]]; then exec {manifest_fd}>&-; ((invalid_count += 1)); continue; fi
|
||||
if [[ -n ${LIST_BACKUPS_REPLACE_MANIFEST:-} && -z ${LIST_BACKUPS_REPLACE_DONE:-} && $path == "${BACKUP_DIR%/}"/* ]]; then
|
||||
if transaction_test_hooks_enabled && path_components_safe "$LIST_BACKUPS_REPLACE_MANIFEST" && [[ -f $LIST_BACKUPS_REPLACE_MANIFEST && ! -L $LIST_BACKUPS_REPLACE_MANIFEST ]]; then
|
||||
local list_manifest_old=${path}/.manifest-list-old
|
||||
rm -f -- "$list_manifest_old"
|
||||
mv -- "$path/manifest" "$list_manifest_old" && cp -- "$LIST_BACKUPS_REPLACE_MANIFEST" "$path/manifest" && chmod 600 -- "$path/manifest"
|
||||
LIST_BACKUPS_REPLACE_DONE=1
|
||||
fi
|
||||
fi
|
||||
if ! validate_backup_manifest "$path"; then
|
||||
[[ -e ${path}/.manifest-list-old ]] && { rm -f -- "$path/manifest"; mv -- "$path/.manifest-list-old" "$path/manifest"; }
|
||||
exec {manifest_fd}>&-
|
||||
((invalid_count += 1)); continue
|
||||
fi
|
||||
manifest_identity_after=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$path/manifest" 2>/dev/null) || {
|
||||
[[ -e ${path}/.manifest-list-old ]] && { rm -f -- "$path/manifest"; mv -- "$path/.manifest-list-old" "$path/manifest"; }
|
||||
exec {manifest_fd}>&-
|
||||
((invalid_count += 1)); continue
|
||||
}
|
||||
if [[ $manifest_identity_before != "$manifest_identity_after" ]]; then
|
||||
[[ -e ${path}/.manifest-list-old ]] && { rm -f -- "$path/manifest"; mv -- "$path/.manifest-list-old" "$path/manifest"; }
|
||||
exec {manifest_fd}>&-
|
||||
((invalid_count += 1)); continue
|
||||
fi
|
||||
name=${path##*/}; timestamp=''; operation=''; target=''
|
||||
while IFS= read -r line; do
|
||||
value=${line#*=}
|
||||
case $line in TIMESTAMP=*) timestamp=$value;; OPERATION=*) operation=$value;; TARGET=*) target=$value;; esac
|
||||
done <&$manifest_fd
|
||||
exec {manifest_fd}>&-
|
||||
entries+=("${name}"$'\t'"${timestamp}"$'\t'"${operation}"$'\t'"${target}")
|
||||
done
|
||||
if ((${#entries[@]} == 0)); then printf 'Резервные копии отсутствуют\n'; ((invalid_count > 0)) && printf 'Предупреждение: пропущено некорректных резервных копий: %d\n' "$invalid_count" >&2; return 0; fi
|
||||
printf '%s\n' "${entries[@]}" | LC_ALL=C sort -r
|
||||
((invalid_count > 0)) && printf 'Предупреждение: пропущено некорректных резервных копий: %d\n' "$invalid_count" >&2
|
||||
return 0
|
||||
}
|
||||
list_audit() {
|
||||
local limit=0 line count=0 mode owner server_owner audit_file fd expected_identity opened_identity locked_identity
|
||||
local -a records=() audit_lines=()
|
||||
(($# <= 1)) || { die "$EXIT_USAGE" 'Лишние аргументы log'; return $?; }
|
||||
(($# == 1)) && { [[ $1 =~ ^[1-9][0-9]{0,3}$ ]] && (( 10#$1 <= 1000 )) || { die "$EXIT_USAGE" 'Некорректный лимит log'; return $?; }; limit=$1; }
|
||||
load_config || return $?
|
||||
audit_file=$LOG_DIR/wg-manager.log
|
||||
path_components_safe "$LOG_DIR" || { die "$EXIT_VALIDATION" 'Каталог журнала небезопасен'; return $?; }
|
||||
[[ -d $LOG_DIR && ! -L $LOG_DIR ]] || { die "$EXIT_VALIDATION" 'Каталог журнала недоступен'; return $?; }
|
||||
[[ -e $audit_file ]] || { printf 'Журнал отсутствует\n'; return 0; }
|
||||
[[ -f $audit_file && ! -L $audit_file ]] || { die "$EXIT_VALIDATION" 'Файл журнала небезопасен'; return $?; }
|
||||
mode=$(stat -c '%a' -- "$LOG_DIR") || return "$EXIT_OPERATION"; [[ $mode == 700 ]] || { die "$EXIT_VALIDATION" 'Небезопасные права каталога журнала'; return $?; }
|
||||
mode=$(stat -c '%a' -- "$audit_file") || return "$EXIT_OPERATION"; [[ $mode == 600 ]] || { die "$EXIT_VALIDATION" 'Небезопасные права журнала'; return $?; }
|
||||
[[ $(stat -c '%h' -- "$audit_file") == 1 ]] || { die "$EXIT_VALIDATION" 'Журнал имеет небезопасные ссылки'; return $?; }
|
||||
server_owner=$(stat -c '%u:%g' -- "$WG_CONF") || return "$EXIT_OPERATION"
|
||||
[[ $(stat -c '%u:%g' -- "$LOG_DIR") == "$server_owner" && $(stat -c '%u:%g' -- "$audit_file") == "$server_owner" ]] || { die "$EXIT_VALIDATION" 'Недоверенный владелец журнала'; return $?; }
|
||||
expected_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$audit_file") || return "$EXIT_OPERATION"
|
||||
exec {fd}<"$audit_file" || return "$EXIT_OPERATION"
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${fd}") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
[[ $opened_identity == "$expected_identity" ]] || { exec {fd}>&-; die "$EXIT_VALIDATION" 'Файл журнала изменился при чтении'; return $?; }
|
||||
if ! flock -s "$fd"; then
|
||||
exec {fd}>&-
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
locked_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$audit_file") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${fd}") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
[[ $locked_identity == "$opened_identity" ]] || { exec {fd}>&-; die "$EXIT_VALIDATION" 'Файл журнала изменился при чтении'; return $?; }
|
||||
mapfile -t audit_lines <&$fd
|
||||
for line in "${audit_lines[@]}"; do
|
||||
[[ $line =~ ^timestamp=[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\ actor_uid=[0-9]+\ operation=[a-z][a-z0-9-]{0,31}\ target=[A-Za-z0-9._-]{1,96}\ client=[A-Za-z0-9._-]{1,96}\ key_suffix=[A-Za-z0-9+/=-]{0,16}\ outcome=[a-z][a-z0-9-]{0,31}\ error=[a-z][a-z0-9-]{0,31}$ ]] || continue
|
||||
records+=("$line")
|
||||
done
|
||||
exec {fd}>&-
|
||||
((${#records[@]} > 0)) || { printf 'В журнале нет корректных записей\n'; return 0; }
|
||||
count=0
|
||||
for ((count=${#records[@]}-1; count>=0; count--)); do
|
||||
printf '%s\n' "${records[count]}"
|
||||
((limit > 0 && ${#records[@]} - count >= limit)) && break
|
||||
done
|
||||
}
|
||||
restore_builder() {
|
||||
local stage=$1
|
||||
[[ -n ${RESTORE_SNAPSHOT:-} && -f $RESTORE_SNAPSHOT && ! -L $RESTORE_SNAPSHOT ]] || return "$EXIT_VALIDATION"
|
||||
cp -- "$RESTORE_SNAPSHOT" "$stage" && chmod 600 -- "$stage"
|
||||
}
|
||||
cleanup_restore_snapshot() {
|
||||
if [[ -n ${RESTORE_SNAPSHOT:-} ]]; then rm -f -- "$RESTORE_SNAPSHOT"; fi
|
||||
unset RESTORE_SNAPSHOT RESTORE_BACKUP RESTORE_BACKUP_DIGEST
|
||||
}
|
||||
restore_backup() {
|
||||
(($# >= 1)) || { die "$EXIT_USAGE" 'Ожидается идентификатор резервной копии'; return $?; }
|
||||
local name=$1 flag='' backup status answer source backup_name timestamp counter reconstructed snapshot_fd expected_identity opened_identity live_owner replacement_backup=''
|
||||
(($# >= 2)) && flag=$2
|
||||
(($# <= 2)) || { die "$EXIT_USAGE" 'Лишние аргументы restore'; return $?; }
|
||||
load_config || return $?
|
||||
backup_name=$name; counter=${backup_name##*-}; timestamp=${backup_name%-"$counter"}; timestamp=${timestamp%-"$WG_INTERFACE"}
|
||||
[[ $timestamp =~ ^[0-9]{8}T[0-9]{6}Z$ && $counter =~ ^[0-9]{3}$ ]] || { die "$EXIT_VALIDATION" 'Некорректный идентификатор резервной копии'; return $?; }
|
||||
reconstructed="${timestamp}-${WG_INTERFACE}-${counter}"
|
||||
[[ $reconstructed == "$name" ]] || { die "$EXIT_VALIDATION" 'Некорректный идентификатор резервной копии'; return $?; }
|
||||
[[ -z $flag || $flag == --yes ]] || { die "$EXIT_USAGE" 'Разрешён только флаг restore --yes'; return $?; }
|
||||
require_add_privilege || return $?
|
||||
backup=$BACKUP_DIR/$name
|
||||
source=$backup/wg.conf
|
||||
live_owner=$(stat -c '%u:%g' -- "$WG_CONF") || { die "$EXIT_OPERATION" 'Не удалось проверить владельца конфигурации'; return $?; }
|
||||
[[ -d $backup && ! -L $backup && -f $source && ! -L $source ]] || { die "$EXIT_VALIDATION" 'Резервная копия не найдена'; return $?; }
|
||||
expected_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$source") || { die "$EXIT_OPERATION" 'Не удалось проверить резервную копию'; return $?; }
|
||||
validate_backup_manifest "$backup" || { die "$EXIT_VALIDATION" 'Резервная копия не прошла проверку'; return $?; }
|
||||
[[ $backup == "${BACKUP_DIR%/}"/* && ${backup%/*} == "${BACKUP_DIR%/}" && ! -L $source && $(stat -c '%a' -- "$backup") == 700 && $(stat -c '%u:%g' -- "$backup") == "$live_owner" && $(stat -c '%a:%h:%u:%g' -- "$backup/manifest") == "600:1:$live_owner" && $(stat -c '%a:%h:%u:%g' -- "$source") == "600:1:$live_owner" ]] || { die "$EXIT_VALIDATION" 'Некорректная безопасность резервной копии'; return $?; }
|
||||
parse_wireguard_config "$source" >/dev/null 2>&1 || { die "$EXIT_VALIDATION" 'Конфигурация резервной копии не прошла проверку'; return $?; }
|
||||
printf 'Резервная копия: %s; интерфейс: %s\n' "$timestamp" "$WG_INTERFACE" >&2
|
||||
[[ -n $flag || ( -t 0 && -t 1 ) ]] || { die "$EXIT_USAGE" 'В неинтерактивном режиме требуется --yes'; return $?; }
|
||||
if [[ -z $flag ]]; then printf 'Восстановить резервную копию %s? [y/N] ' "$name" >&2; IFS= read -r answer || answer=n; if [[ $answer != y && $answer != Y ]]; then if ! write_audit_event restore "$name" cancelled cancelled >/dev/null 2>&1; then warn 'Не удалось записать аудит отмены восстановления'; fi; cleanup_restore_snapshot; return 0; fi; fi
|
||||
RESTORE_BACKUP=$backup
|
||||
RESTORE_SNAPSHOT=$(mktemp "${WG_CONF%/*}/.wg-manager.restore.XXXXXX") || { die "$EXIT_OPERATION" 'Не удалось подготовить снимок восстановления'; return $?; }
|
||||
chmod 600 -- "$RESTORE_SNAPSHOT" || { cleanup_restore_snapshot; die "$EXIT_OPERATION" 'Не удалось защитить снимок восстановления'; return $?; }
|
||||
[[ $(stat -c '%d:%i:%a:%u:%g:%h' -- "$source") == "$expected_identity" ]] || { cleanup_restore_snapshot; die "$EXIT_VALIDATION" 'Резервная копия изменилась'; return $?; }
|
||||
if [[ -n ${RESTORE_PREOPEN_REPLACEMENT:-} ]]; then
|
||||
if ! transaction_test_hooks_enabled || ! path_components_safe "$RESTORE_PREOPEN_REPLACEMENT" || [[ ! -f $RESTORE_PREOPEN_REPLACEMENT || -L $RESTORE_PREOPEN_REPLACEMENT || $RESTORE_PREOPEN_REPLACEMENT == "$source" ]]; then
|
||||
cleanup_restore_snapshot; die "$EXIT_VALIDATION" 'Некорректный тестовый источник восстановления'; return $?
|
||||
fi
|
||||
replacement_backup=${source}.restore-preopen-old
|
||||
rm -f -- "$replacement_backup"
|
||||
mv -- "$source" "$replacement_backup" || { cleanup_restore_snapshot; die "$EXIT_OPERATION" 'Не удалось подготовить тестовый источник восстановления'; return $?; }
|
||||
cp -- "$RESTORE_PREOPEN_REPLACEMENT" "$source" || { mv -- "$replacement_backup" "$source"; cleanup_restore_snapshot; die "$EXIT_OPERATION" 'Не удалось подготовить тестовый источник восстановления'; return $?; }
|
||||
fi
|
||||
exec {snapshot_fd}<"$source" || { cleanup_restore_snapshot; die "$EXIT_OPERATION" 'Не удалось открыть резервную копию'; return $?; }
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${snapshot_fd}") || { exec {snapshot_fd}>&-; cleanup_restore_snapshot; return "$EXIT_OPERATION"; }
|
||||
if [[ $opened_identity != "$expected_identity" ]]; then
|
||||
exec {snapshot_fd}>&-
|
||||
if [[ -n ${replacement_backup:-} && -e $replacement_backup ]]; then
|
||||
rm -f -- "$source"
|
||||
mv -- "$replacement_backup" "$source"
|
||||
fi
|
||||
cleanup_restore_snapshot
|
||||
die "$EXIT_VALIDATION" 'Резервная копия изменилась'
|
||||
return $?
|
||||
fi
|
||||
cat <&$snapshot_fd >"$RESTORE_SNAPSHOT" || { exec {snapshot_fd}>&-; cleanup_restore_snapshot; return "$EXIT_OPERATION"; }
|
||||
exec {snapshot_fd}>&-
|
||||
parse_wireguard_config "$RESTORE_SNAPSHOT" >/dev/null 2>&1 || { cleanup_restore_snapshot; die "$EXIT_VALIDATION" 'Снимок восстановления не прошёл проверку'; return $?; }
|
||||
if run_transaction restore "$name" restore_builder; then status=0; else status=$?; fi
|
||||
if (( status == 0 )); then
|
||||
write_audit_event restore "$name" success success >/dev/null 2>&1 || { cleanup_restore_snapshot; die "$EXIT_OPERATION" 'Восстановление выполнено, но аудит не записан'; return "$EXIT_OPERATION"; }
|
||||
else
|
||||
if ! write_audit_event restore "$name" failed transaction_failed >/dev/null 2>&1; then warn 'Не удалось записать аудит восстановления'; fi
|
||||
fi
|
||||
cleanup_restore_snapshot
|
||||
return "$status"
|
||||
}
|
||||
DIAG_FAILS=0
|
||||
DIAG_QUIET=0
|
||||
declare -A DIAG_IDS=()
|
||||
@@ -1885,8 +2151,10 @@ archive_remove_artifacts() {
|
||||
done
|
||||
}
|
||||
|
||||
remove_audit() {
|
||||
local outcome=$1 audit_file=${LOG_DIR}/wg-manager.log mode owner server_owner timestamp fd expected_identity opened_identity
|
||||
write_audit_event() {
|
||||
local operation=$1 target=$2 outcome=$3 error_category=$4
|
||||
local audit_file=${LOG_DIR}/wg-manager.log mode owner server_owner timestamp fd expected_identity opened_identity locked_identity write_status
|
||||
[[ $operation =~ ^[a-z][a-z0-9-]{0,31}$ && $target =~ ^[A-Za-z0-9._-]{1,96}$ && $outcome =~ ^[a-z][a-z0-9-]{0,31}$ && $error_category =~ ^[a-z][a-z0-9-]{0,31}$ ]] || return "$EXIT_VALIDATION"
|
||||
path_components_safe "$LOG_DIR" || return "$EXIT_VALIDATION"
|
||||
[[ ! -L $LOG_DIR ]] || return "$EXIT_VALIDATION"
|
||||
if [[ ! -e $LOG_DIR ]]; then
|
||||
@@ -1909,8 +2177,11 @@ remove_audit() {
|
||||
owner=$(stat -c '%u:%g' -- "$audit_file") || return "$EXIT_OPERATION"
|
||||
[[ $owner == "$server_owner" ]] || return "$EXIT_VALIDATION"
|
||||
else
|
||||
: >"$audit_file" || return "$EXIT_OPERATION"
|
||||
chmod 600 -- "$audit_file" || return "$EXIT_OPERATION"
|
||||
if ! (umask 077; set -C; : >"$audit_file") 2>/dev/null; then
|
||||
[[ -f $audit_file && ! -L $audit_file ]] || return "$EXIT_OPERATION"
|
||||
else
|
||||
chmod 600 -- "$audit_file" || return "$EXIT_OPERATION"
|
||||
fi
|
||||
fi
|
||||
timestamp=$(current_epoch) || return "$EXIT_OPERATION"
|
||||
timestamp=$(date -u -d "@${timestamp}" +%Y-%m-%dT%H:%M:%SZ) || return "$EXIT_OPERATION"
|
||||
@@ -1918,11 +2189,18 @@ remove_audit() {
|
||||
exec {fd}>>"$audit_file" || return "$EXIT_OPERATION"
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${fd}") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
[[ $opened_identity == "$expected_identity" ]] || { exec {fd}>&-; return "$EXIT_VALIDATION"; }
|
||||
if ! printf 'operation=remove client=%s target=%s key_suffix=%s timestamp=%s outcome=%s actor_uid=%s error=%s\n' "$REMOVE_NAME" "$REMOVE_NAME" "$(short_public_key "$REMOVE_KEY")" "$timestamp" "$outcome" "$EUID" "${REMOVE_ERROR_CATEGORY:-$outcome}" >&$fd; then
|
||||
exec {fd}>&-
|
||||
return "$EXIT_OPERATION"
|
||||
fi
|
||||
if ! flock "$fd"; then exec {fd}>&-; return "$EXIT_OPERATION"; fi
|
||||
locked_identity=$(stat -c '%d:%i:%a:%u:%g:%h' -- "$audit_file") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
opened_identity=$(stat -Lc '%d:%i:%a:%u:%g:%h' -- "/proc/self/fd/${fd}") || { exec {fd}>&-; return "$EXIT_OPERATION"; }
|
||||
[[ $locked_identity == "$opened_identity" ]] || { exec {fd}>&-; return "$EXIT_VALIDATION"; }
|
||||
if printf 'timestamp=%s actor_uid=%s operation=%s target=%s client=%s key_suffix=%s outcome=%s error=%s\n' "$timestamp" "$EUID" "$operation" "$target" "${AUDIT_CLIENT:-$target}" "${AUDIT_KEY_SUFFIX:-}" "$outcome" "$error_category" >&$fd; then write_status=0; else write_status=$EXIT_OPERATION; fi
|
||||
exec {fd}>&-
|
||||
return "$write_status"
|
||||
}
|
||||
|
||||
remove_audit() {
|
||||
local outcome=$1
|
||||
AUDIT_CLIENT=${REMOVE_NAME:-remove} AUDIT_KEY_SUFFIX=$(short_public_key "${REMOVE_KEY:-}") write_audit_event remove "${REMOVE_NAME:-remove}" "$outcome" "${REMOVE_ERROR_CATEGORY:-$outcome}"
|
||||
}
|
||||
|
||||
confirm_remove() {
|
||||
@@ -2018,6 +2296,17 @@ main() {
|
||||
diagnose "$@"
|
||||
return $?
|
||||
fi
|
||||
if [[ $1 == restore ]]; then
|
||||
(($# >= 2 && $# <= 3)) || { die "$EXIT_USAGE" 'Ожидается идентификатор резервной копии'; return $?; }
|
||||
shift
|
||||
restore_backup "$@"
|
||||
return $?
|
||||
fi
|
||||
if [[ $1 == log ]]; then
|
||||
shift
|
||||
list_audit "$@"
|
||||
return $?
|
||||
fi
|
||||
if (( $# != 1 )); then
|
||||
if die "$EXIT_USAGE" 'Ожидается одна команда'; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
@@ -2032,7 +2321,9 @@ main() {
|
||||
shift
|
||||
(($# >= 1)) || { die "$EXIT_USAGE" 'Ожидается имя клиента'; return $?; }
|
||||
remove_client "$@" ;;
|
||||
logs|backups|restore|tui) run_placeholder "$1" ;;
|
||||
backup) manual_backup ;;
|
||||
backups) list_backups ;;
|
||||
logs|tui) run_placeholder "$1" ;;
|
||||
*)
|
||||
if die "$EXIT_USAGE" "неизвестная команда: $1"; then status=0; else status=$?; fi
|
||||
print_usage >&2
|
||||
|
||||
Reference in New Issue
Block a user