fix34: устранён import-time баг i18n, усилен SSH tunnel policy и doctor warning
This commit is contained in:
@@ -1,10 +1,15 @@
|
|||||||
import { createI18n } from "vue-i18n";
|
import { createI18n } from "vue-i18n";
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import defaultSettings from "@/settings";
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
import enLocale from "./package/en";
|
import enLocale from "./package/en";
|
||||||
import ruLocale from "./package/ru";
|
import ruLocale from "./package/ru";
|
||||||
|
|
||||||
|
function initialLocale(): string {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return defaultSettings.language || "ru";
|
||||||
|
}
|
||||||
|
return window.localStorage.getItem("language") || defaultSettings.language || "ru";
|
||||||
|
}
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
ru: {
|
ru: {
|
||||||
...ruLocale,
|
...ruLocale,
|
||||||
@@ -16,7 +21,8 @@ const messages = {
|
|||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: appStore.language,
|
locale: initialLocale(),
|
||||||
|
fallbackLocale: "ru",
|
||||||
messages: messages,
|
messages: messages,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import "nprogress/nprogress.css";
|
|||||||
|
|
||||||
NProgress.configure({ showSpinner: false }); // Индикатор загрузки
|
NProgress.configure({ showSpinner: false }); // Индикатор загрузки
|
||||||
|
|
||||||
const permissionStore = usePermissionStoreHook();
|
|
||||||
|
|
||||||
// Разрешённые маршруты
|
// Разрешённые маршруты
|
||||||
const whiteList = ["/login", "/register"];
|
const whiteList = ["/login", "/register"];
|
||||||
|
|
||||||
@@ -37,6 +35,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { roles, forcePasswordChange } = await adminStore.getAdminInfo();
|
const { roles, forcePasswordChange } = await adminStore.getAdminInfo();
|
||||||
|
const permissionStore = usePermissionStoreHook();
|
||||||
const accessRoutes = permissionStore.generateRoutes(roles);
|
const accessRoutes = permissionStore.generateRoutes(roles);
|
||||||
accessRoutes.forEach((route) => {
|
accessRoutes.forEach((route) => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
@@ -68,4 +67,3 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,50 @@ curl -sS \
|
|||||||
- совпадает ли `HUI_INSTALL_DIR` с реальностью
|
- совпадает ли `HUI_INSTALL_DIR` с реальностью
|
||||||
- не сломан ли bind host / port
|
- не сломан ли bind host / port
|
||||||
|
|
||||||
|
### Admin UI access via SSH tunnel
|
||||||
|
|
||||||
|
Production-модель для UI: `HY2XS_UI_BIND_HOST=127.0.0.1`, внешний доступ к `8080/tcp` не открывается.
|
||||||
|
Доступ оператора выполняется через SSH local forwarding.
|
||||||
|
|
||||||
|
Windows-команда туннеля:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 2323 \
|
||||||
|
-i C:\Users\kirap\.ssh\id_ed25519_uk1 \
|
||||||
|
-N \
|
||||||
|
-L 127.0.0.1:8080:127.0.0.1:8080 \
|
||||||
|
root@185.156.108.141
|
||||||
|
```
|
||||||
|
|
||||||
|
После запуска открыть `http://127.0.0.1:8080/#/login`.
|
||||||
|
|
||||||
|
Если SSH-туннель не поднимается (`administratively prohibited`), проверить effective SSH policy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
|
||||||
|
```
|
||||||
|
|
||||||
|
Рекомендуемый фрагмент hardening `sshd_config`:
|
||||||
|
|
||||||
|
```sshconfig
|
||||||
|
Port 2323
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
PasswordAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
PermitRootLogin prohibit-password
|
||||||
|
|
||||||
|
AllowTcpForwarding local
|
||||||
|
PermitOpen 127.0.0.1:8080 localhost:8080
|
||||||
|
GatewayPorts no
|
||||||
|
|
||||||
|
X11Forwarding no
|
||||||
|
AllowAgentForwarding no
|
||||||
|
MaxAuthTries 3
|
||||||
|
LoginGraceTime 20
|
||||||
|
ClientAliveInterval 300
|
||||||
|
ClientAliveCountMax 2
|
||||||
|
```
|
||||||
|
|
||||||
### Hysteria скачалась, но не стартует
|
### Hysteria скачалась, но не стартует
|
||||||
Проверить:
|
Проверить:
|
||||||
- валиден ли config
|
- валиден ли config
|
||||||
|
|||||||
@@ -99,7 +99,51 @@ hy2xs-orchestrator doctor --package-dir /usr/local/lib/hy2xs/package --config /e
|
|||||||
|
|
||||||
Команда выполняет preflight + smoke как post-install/post-reboot validation.
|
Команда выполняет preflight + smoke как post-install/post-reboot validation.
|
||||||
|
|
||||||
## 13. Secret-safe config sharing
|
## 13. Admin UI access via SSH tunnel
|
||||||
|
|
||||||
|
Production policy: UI остаётся loopback-only (`HY2XS_UI_BIND_HOST=127.0.0.1`), внешний доступ к `8080/tcp` не открывается.
|
||||||
|
Операторский доступ выполняется через SSH local forwarding.
|
||||||
|
|
||||||
|
Windows tunnel command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -p 2323 \
|
||||||
|
-i C:\Users\kirap\.ssh\id_ed25519_uk1 \
|
||||||
|
-N \
|
||||||
|
-L 127.0.0.1:8080:127.0.0.1:8080 \
|
||||||
|
root@185.156.108.141
|
||||||
|
```
|
||||||
|
|
||||||
|
Open in browser: `http://127.0.0.1:8080/#/login`.
|
||||||
|
|
||||||
|
Если туннель падает с `administratively prohibited`, проверить effective sshd-конфиг:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended sshd hardening fragment:
|
||||||
|
|
||||||
|
```sshconfig
|
||||||
|
Port 2323
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
PasswordAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
PermitRootLogin prohibit-password
|
||||||
|
|
||||||
|
AllowTcpForwarding local
|
||||||
|
PermitOpen 127.0.0.1:8080 localhost:8080
|
||||||
|
GatewayPorts no
|
||||||
|
|
||||||
|
X11Forwarding no
|
||||||
|
AllowAgentForwarding no
|
||||||
|
MaxAuthTries 3
|
||||||
|
LoginGraceTime 20
|
||||||
|
ClientAliveInterval 300
|
||||||
|
ClientAliveCountMax 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 14. Secret-safe config sharing
|
||||||
|
|
||||||
Для передачи конфигов в тикеты/чаты используйте встроенную redaction-команду:
|
Для передачи конфигов в тикеты/чаты используйте встроенную redaction-команду:
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,61 @@
|
|||||||
import type { ReconfigureContext, ReconfigureOptions } from "../types/context";
|
import type { ReconfigureContext, ReconfigureOptions } from "../types/context";
|
||||||
import { readText } from "../lib/fs";
|
import { readText } from "../lib/fs";
|
||||||
import { setOperationContext, step, stepDone } from "../lib/log";
|
import { info, setOperationContext, step, stepDone } from "../lib/log";
|
||||||
import { parseRuntimeEnv } from "../config/env";
|
import { parseRuntimeEnv } from "../config/env";
|
||||||
import { preflight } from "../steps/preflight";
|
import { preflight } from "../steps/preflight";
|
||||||
import { smoke } from "../steps/smoke";
|
import { smoke } from "../steps/smoke";
|
||||||
import { readInstalledHysteriaVersion, readPackageValue } from "../lib/packageMeta";
|
import { readInstalledHysteriaVersion, readPackageValue } from "../lib/packageMeta";
|
||||||
|
import { run } from "../lib/process";
|
||||||
|
|
||||||
|
function hasPermitOpenForLocalUi(value: string): boolean {
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (normalized === "any") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return normalized.split(/\s+/).includes("127.0.0.1:8080") || normalized.split(/\s+/).includes("localhost:8080");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSshForwardingForLocalUi(uiBindHost: string): Promise<void> {
|
||||||
|
if (uiBindHost !== "127.0.0.1") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sshdConfigText = await run`sshd -T`;
|
||||||
|
const lines = sshdConfigText.split(/\r?\n/);
|
||||||
|
const effective = new Map<string, string>();
|
||||||
|
for (const line of lines) {
|
||||||
|
const normalized = line.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const separator = normalized.indexOf(" ");
|
||||||
|
if (separator <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const key = normalized.slice(0, separator).trim();
|
||||||
|
const value = normalized.slice(separator + 1).trim();
|
||||||
|
effective.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowTcpForwarding = (effective.get("allowtcpforwarding") || "").toLowerCase();
|
||||||
|
const permitOpen = effective.get("permitopen") || "";
|
||||||
|
|
||||||
|
const forwardingEnabled = allowTcpForwarding === "yes" || allowTcpForwarding === "all" || allowTcpForwarding === "local";
|
||||||
|
const permitOpenValid = hasPermitOpenForLocalUi(permitOpen);
|
||||||
|
|
||||||
|
if (!forwardingEnabled || !permitOpenValid) {
|
||||||
|
info(
|
||||||
|
"WARNING: UI is local-only, but SSH local forwarding is disabled or restricted (allowtcpforwarding/permitopen). Verify sshd policy for 127.0.0.1:8080 tunnel access."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
info("WARNING: unable to read effective sshd config via `sshd -T`; skipping SSH forwarding advisory check.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function doctor(options: ReconfigureOptions): Promise<void> {
|
export async function doctor(options: ReconfigureOptions): Promise<void> {
|
||||||
setOperationContext(`doctor-${Date.now().toString(36)}`);
|
setOperationContext(`doctor-${Date.now().toString(36)}`);
|
||||||
@@ -24,6 +75,9 @@ export async function doctor(options: ReconfigureOptions): Promise<void> {
|
|||||||
step("doctor preflight");
|
step("doctor preflight");
|
||||||
await preflight(context);
|
await preflight(context);
|
||||||
stepDone("doctor preflight");
|
stepDone("doctor preflight");
|
||||||
|
|
||||||
|
await checkSshForwardingForLocalUi(context.config.uiBindHost);
|
||||||
|
|
||||||
step("doctor smoke");
|
step("doctor smoke");
|
||||||
await smoke(context);
|
await smoke(context);
|
||||||
stepDone("doctor smoke");
|
stepDone("doctor smoke");
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ run_fix20_acceptance_subset() {
|
|||||||
log_step "Acceptance: bootstrap peer can pass auth smoke"
|
log_step "Acceptance: bootstrap peer can pass auth smoke"
|
||||||
grep -q 'quota := int64(-1)' apps/dao/sqlite.go || fail "acceptance: bootstrap peer quota must be unlimited (-1), otherwise install auth smoke fails"
|
grep -q 'quota := int64(-1)' apps/dao/sqlite.go || fail "acceptance: bootstrap peer quota must be unlimited (-1), otherwise install auth smoke fails"
|
||||||
|
|
||||||
|
log_step "Acceptance: frontend i18n does not touch Pinia at module import"
|
||||||
|
! grep -q 'useAppStore' apps/frontend/src/lang/index.ts || fail "acceptance: lang/index.ts must not import/use Pinia store"
|
||||||
|
|
||||||
log_step "Acceptance: env rendering maps machine token and fails on unresolved placeholders"
|
log_step "Acceptance: env rendering maps machine token and fails on unresolved placeholders"
|
||||||
grep -q 'HYSTERIA_API_SECRET: context.config.hysteriaTrafficStatsSecret' orchestrator/src/steps/env.ts || fail "acceptance: writePostInstallEnv must pass HYSTERIA_API_SECRET"
|
grep -q 'HYSTERIA_API_SECRET: context.config.hysteriaTrafficStatsSecret' orchestrator/src/steps/env.ts || fail "acceptance: writePostInstallEnv must pass HYSTERIA_API_SECRET"
|
||||||
grep -q 'template render failed: unresolved placeholders' orchestrator/src/lib/fs.ts || fail "acceptance: renderTemplate must fail on unresolved placeholders"
|
grep -q 'template render failed: unresolved placeholders' orchestrator/src/lib/fs.ts || fail "acceptance: renderTemplate must fail on unresolved placeholders"
|
||||||
|
|||||||
Reference in New Issue
Block a user