fix(fix3): runtime env hardening and public endpoint source-of-truth
This commit is contained in:
@@ -4,7 +4,7 @@ import { runVisible } from "../lib/process";
|
||||
|
||||
export async function generateConfig(context: InstallContext): Promise<void> {
|
||||
const tlsAcmeBlock = context.config.tlsMode === "acme"
|
||||
? `acme:\n domains:\n - ${context.config.domain}\n email: ${context.config.acmeEmail}\n ca: letsencrypt\n dir: /var/lib/hysteria/acme\n listenHost: 0.0.0.0`
|
||||
? `acme:\n domains:\n - ${context.config.domain}\n email: ${context.config.acmeEmail}\n ca: letsencrypt\n dir: /var/lib/hysteria/acme\n listenHost: 0.0.0.0\n type: ${context.config.acmeType}`
|
||||
: "";
|
||||
const tlsFileBlock = context.config.tlsMode === "file" || context.config.tlsMode === "self_signed_dev"
|
||||
? `tls:\n cert: ${context.config.tlsCertPath}\n key: ${context.config.tlsKeyPath}`
|
||||
|
||||
@@ -12,6 +12,15 @@ async function isPortBusy(port: number): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
async function isUnitActive(unit: string): Promise<boolean> {
|
||||
try {
|
||||
await run`systemctl is-active --quiet ${unit}`;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function preflight(context: InstallContext): Promise<void> {
|
||||
const isReconfigure = context.packageVersion === "reconfigure";
|
||||
|
||||
@@ -76,11 +85,24 @@ export async function preflight(context: InstallContext): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (await isPortBusy(context.config.hysteriaPort)) {
|
||||
fail(`Hysteria UDP/TCP port already appears to be in use: ${context.config.hysteriaPort}`);
|
||||
const hysteriaPortBusy = await isPortBusy(context.config.hysteriaPort);
|
||||
const uiPortBusy = await isPortBusy(context.config.uiPort);
|
||||
|
||||
if (!isReconfigure) {
|
||||
if (hysteriaPortBusy) {
|
||||
fail(`Hysteria UDP/TCP port already appears to be in use: ${context.config.hysteriaPort}`);
|
||||
}
|
||||
if (uiPortBusy) {
|
||||
fail(`HY2XS admin port already appears to be in use: ${context.config.uiPort}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (await isPortBusy(context.config.uiPort)) {
|
||||
fail(`HY2XS admin port already appears to be in use: ${context.config.uiPort}`);
|
||||
if (hysteriaPortBusy && !(await isUnitActive("hysteria-server"))) {
|
||||
fail(`Hysteria port ${context.config.hysteriaPort} is occupied by a non-HY2XS process`);
|
||||
}
|
||||
|
||||
if (uiPortBusy && !(await isUnitActive("hy2xs-admin"))) {
|
||||
fail(`HY2XS admin port ${context.config.uiPort} is occupied by a non-HY2XS process`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export async function smoke(context: InstallContext): Promise<void> {
|
||||
await runVisible`test -s /etc/hy2xs/hy2xs.env`;
|
||||
await runVisible`test -s /etc/hysteria/post-install.env`;
|
||||
await runVisible`test -s ${context.config.bootstrapAdminSecretPath}`;
|
||||
await runVisible`grep -q '^${context.config.adminUser}:' ${context.config.bootstrapAdminSecretPath}`;
|
||||
await runVisible`test "$(stat -c '%a' /etc/hysteria/config.yaml)" = '600'`;
|
||||
await runVisible`test "$(stat -c '%a' /etc/hy2xs/hy2xs.env)" = '600'`;
|
||||
await runVisible`test "$(stat -c '%a' /etc/hysteria/post-install.env)" = '600'`;
|
||||
@@ -26,10 +27,18 @@ export async function smoke(context: InstallContext): Promise<void> {
|
||||
}
|
||||
await runVisible`ss -H -lun | grep -q '0.0.0.0:${context.config.hysteriaPort} '`;
|
||||
await runVisible`! ss -H -ltnu | grep -q '\[::\]'`;
|
||||
await runHidden`curl -fsS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth >/dev/null`;
|
||||
const invalidAuthResponse = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||
if (!/"ok"\s*:\s*false/.test(invalidAuthResponse)) {
|
||||
throw new Error(`unexpected auth response for invalid credentials: ${invalidAuthResponse}`);
|
||||
}
|
||||
|
||||
const validAuthResponse = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${context.config.adminUser}.${context.config.adminInitialPassword}","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||
if (!/"ok"\s*:\s*true/.test(validAuthResponse)) {
|
||||
throw new Error(`unexpected auth response for valid credentials`);
|
||||
}
|
||||
|
||||
await runHidden`curl -fsS --max-time 5 -H 'Authorization: ${context.config.hysteriaTrafficStatsSecret}' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online >/dev/null`;
|
||||
const deniedCode = await runSecret`curl -fsS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online`;
|
||||
const deniedCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online`;
|
||||
if (!/(401|403)/.test(deniedCode)) {
|
||||
throw new Error(`unexpected trafficStats status for invalid secret: ${deniedCode}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user