package constant // Ключи таблицы `config` — то, чем действительно владеет админка. // // Сетевых настроек самой панели (порт, web-контекст, пути к сертификатам) // здесь больше нет. Они достались от H UI, где панель конфигурировала себя // сама, и в архитектуре HY2XS были вторым источником истины: порт приходит из // HY2XS_UI_PORT через systemd, bind — из HY2XS_UI_BIND_HOST, TLS // терминируется на внешнем слое. Панель записывала в SQLite тот же порт, // который ей передали аргументом, и тут же читала его обратно. // Ключей HYSTERIA2_ENABLE, HYSTERIA2_CONFIG, HYSTERIA2_TRAFFIC_TIME и // HYSTERIA2_CONFIG_REMARK здесь больше нет. Все четыре достались от предыдущего // поколения панели и к v1 перестали чем-либо управлять: // // HYSTERIA2_ENABLE — жизненным циклом Hysteria владеет systemd, а // единственным потребителем ключа была строка лога; // HYSTERIA2_CONFIG — второй источник истины рядом с // /etc/hysteria/config.yaml, причём читался ПЕРВЫМ; // HYSTERIA2_TRAFFIC_TIME — настройка в панели без единого потребителя в // рантайме: интервал сбора метрик задан в коде; // HYSTERIA2_CONFIG_REMARK — пустая read-only строка, которую никто не писал. // // Пустой ключ, который нельзя изменить и который ни на что не влияет, — это не // «задел на будущее», а состояние, которое приходится объяснять в документации // и защищать в allowlist. Настоящую замену получил только remark: имя профиля // в share URI теперь выводится из имени пира (см. service/hysteria2_api.go). const ( JwtSecret = "JWT_SECRET" PeerSecretKey = "PEER_SECRET_KEY" PeerSecretEncryptionKey = "PEER_SECRET_ENCRYPTION_KEY" Hysteria2TrafficStatsSecret = "HYSTERIA2_TRAFFIC_STATS_SECRET" ResetTrafficCron = "RESET_TRAFFIC_CRON" // BootstrapPeerSeeded — отметка о том, что пир установщика УЖЕ был создан // однажды. Не «существует сейчас», а именно «был создан». // // Различие и есть суть ключа. Раньше единственным признаком служило наличие // строки в таблице пиров, поэтому удаление bootstrap-admin-peer выглядело // отзывом доступа, но им не было: HY2XS_ADMIN_CON_PASS продолжает жить в // /etc/hy2xs/hy2xs.env, и ближайший `systemctl restart hy2xs-admin` или // перезагрузка сервера возвращали тот же самый секрет в базу. Молча. // // Отзыв учётных данных, который не переживает перезапуск, — не отзыв. // Маркер делает удаление окончательным: seeder читает его, а не таблицу // пиров, поэтому после первого успешного bootstrap он не создаёт запись // больше никогда. BootstrapPeerSeeded = "BOOTSTRAP_PEER_SEEDED" ) // Что операторский API имеет право прочитать и записать в таблице `config`. // // Список — allowlist, и это принципиально. Раньше здесь работал denylist из // трёх orchestrator-ключей, а `getConfig`/`listConfig` принимали произвольную // строку. В той же таблице лежат JWT_SECRET, PEER_SECRET_KEY, // PEER_SECRET_ENCRYPTION_KEY и HYSTERIA2_TRAFFIC_STATS_SECRET — то есть // авторизованный запрос `?key=PEER_SECRET_ENCRYPTION_KEY` отдавал master-key // шифрования секретов пиров, а updateConfigs позволял его подменить. После // подмены перестают расшифровываться секреты уже существующих пиров. // // Именно ради этого удаляли generic export/import таблицы `config`, но точечный // API остался прежним, и дыра просто сменила размер отверстия. // // Denylist здесь неверен структурно: он требует, чтобы автор КАЖДОГО нового // ключа вспомнил про этот файл. Забытый ключ при denylist сразу публичен, при // allowlist — сразу закрыт. Отказ по умолчанию — единственное поведение, // которое не зависит от внимательности. var ( // publicReadableConfigKeys — настройки, которые панель показывает оператору. publicReadableConfigKeys = map[string]struct{}{ ResetTrafficCron: {}, } // publicWritableConfigKeys — подмножество читаемых, которые оператор // действительно редактирует в интерфейсе. // // После очистки мёртвых ключей оба списка совпали: единственная настройка, // которой владеет панель, — расписание сброса счётчиков трафика. Списки // намеренно не схлопнуты в один: читаемое и записываемое — разные права, и // первый же ключ «показываем, но менять нельзя» вернёт разницу. publicWritableConfigKeys = map[string]struct{}{ ResetTrafficCron: {}, } ) // IsPublicReadableConfigKey сообщает, можно ли отдать значение ключа наружу. func IsPublicReadableConfigKey(key string) bool { _, ok := publicReadableConfigKeys[key] return ok } // IsPublicWritableConfigKey сообщает, можно ли принять значение ключа снаружи. func IsPublicWritableConfigKey(key string) bool { _, ok := publicWritableConfigKeys[key] return ok } // PublicReadableConfigKeys возвращает копию списка читаемых ключей. // Используется тестами и текстами ошибок. func PublicReadableConfigKeys() []string { return keysOf(publicReadableConfigKeys) } // PublicWritableConfigKeys возвращает копию списка записываемых ключей. func PublicWritableConfigKeys() []string { return keysOf(publicWritableConfigKeys) } // InternalConfigKeys — ключи, которые не покидают процесс ни при каком // сценарии. Объявлены явно, чтобы тест мог проверить их поимённо, а не // полагаться на то, что их «случайно нет» в allowlist. func InternalConfigKeys() []string { return []string{ JwtSecret, PeerSecretKey, PeerSecretEncryptionKey, Hysteria2TrafficStatsSecret, } } func keysOf(set map[string]struct{}) []string { out := make([]string, 0, len(set)) for key := range set { out = append(out, key) } return out }