ssd error

This commit is contained in:
2026-08-27 11:43:56 +05:00
parent 85428defe5
commit df30fcebb6
4 changed files with 68 additions and 31 deletions

View File

@@ -55,6 +55,11 @@ ENCRYPT_TRAFFIC=false
# (comma-separated). Default: only the host from LDAP_URL is used,
# the rest is discovered via DNS SRV (_kerberos._tcp.<domain>).
LDAP_KDC_SERVERS=dc.school25.ru,dc3.school25.ru,srv.school25.ru
# Transient GSSAPI/KDC errors (e.g. "Server (ldap/...@REALM) unknown") are
# retried automatically. Number of attempts (default 3) and pause between them
# in milliseconds (default 1000). Applies to the kerberos password method.
# LDAP_PASSWORD_RETRIES=3
# LDAP_PASSWORD_RETRY_DELAY_MS=1000
# ============ Email (registration) ============
# SMTP server used to send verification codes and registration result letters.

View File

@@ -27,6 +27,8 @@ const config = {
passwordMethod: getEnv('LDAP_PASSWORD_METHOD', passwordMethodDefault),
ldapRealm: getEnv('LDAP_REALM', String(getEnv('LDAP_DOMAIN', 'school25.ru')).toUpperCase()),
kdcServers: getEnv('LDAP_KDC_SERVERS', ''),
passwordRetries: Math.max(1, parseInt(getEnv('LDAP_PASSWORD_RETRIES', '3'), 10) || 3),
passwordRetryDelayMs: Math.max(0, parseInt(getEnv('LDAP_PASSWORD_RETRY_DELAY_MS', '1000'), 10) || 1000),
powerShellExe: getEnv('LDAP_POWERSHELL_EXE', 'powershell.exe'),
emailHost: getEnv('EMAIL_HOST', ''),
emailPort: parseInt(getEnv('EMAIL_PORT', '587'), 10),

View File

@@ -248,7 +248,9 @@ async function setPassword(client, userDN, password) {
await passwordSetter.setPasswordViaKerberos(userDN, password, {
ldapUrl: config.ldapUrl,
principal,
adminPassword: config.adminPassword
adminPassword: config.adminPassword,
retries: config.passwordRetries,
retryDelayMs: config.passwordRetryDelayMs
});
return;
}

View File

@@ -173,48 +173,76 @@ async function setPasswordViaKerberos(userDN, password, {
ldapUrl = '',
principal = '',
adminPassword = '',
ccache = '/tmp/krb5cc_ldapauth'
ccache = '/tmp/krb5cc_ldapauth',
retries = 3,
retryDelayMs = 1000
} = {}) {
if (!principal) {
throw new Error('Не задан principal администратора (ADMIN_LOGIN/LDAP_REALM) для Kerberos');
}
const maxAttempts = retries >= 1 ? retries : 3;
const ccacheEnv = { KRB5CCNAME: ccache };
const kinit = await runChild(exe, ['-c', ccache, principal], {
env: ccacheEnv,
input: String(adminPassword || '') + '\n',
timeoutMs,
describe: 'kinit'
});
if (kinit.code !== 0) {
const detail = String(kinit.stderr || kinit.stdout || '').trim();
throw new Error(`Не удалось получить билет Kerberos (kinit, код ${kinit.code})${detail ? ': ' + detail : ''}`);
async function destroyCache() {
try {
await runChild('kdestroy', ['-c', ccache], {
env: ccacheEnv,
input: '',
timeoutMs: 5000,
describe: 'kdestroy'
});
} catch (_) { /* best-effort cleanup */ }
}
const ldapArgs = ['-Y', 'GSSAPI'];
if (ldapUrl) ldapArgs.push('-H', ldapUrl);
ldapArgs.push('-O', 'maxssf=256');
// Transient GSSAPI/KDC errors (e.g. "Server (ldap/...@REALM) unknown" from
// ldapmodify) often pass on retry — DNS canonicalization or DC selection may
// differ. Each attempt gets a fresh kinit ticket so a partial/poisoned
// ccache can't cause repeated failures. Rewriting the same unicodePwd is
// idempotent, so retrying is safe.
let lastError = null;
const ldapmodify = await runChild(ldapmodifyExe, ldapArgs, {
env: ccacheEnv,
input: buildUnicodePwdLdif(userDN, password),
timeoutMs,
describe: 'ldapmodify'
});
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const kinit = await runChild(exe, ['-c', ccache, principal], {
env: ccacheEnv,
input: String(adminPassword || '') + '\n',
timeoutMs,
describe: 'kinit'
});
if (kinit.code !== 0) {
const detail = String(kinit.stderr || kinit.stdout || '').trim();
throw new Error(`Не удалось получить билет Kerberos (kinit, код ${kinit.code})${detail ? ': ' + detail : ''}`);
}
try {
await runChild('kdestroy', ['-c', ccache], {
env: ccacheEnv,
input: '',
timeoutMs: 5000,
describe: 'kdestroy'
});
} catch (_) { /* best-effort cleanup */ }
const ldapArgs = ['-Y', 'GSSAPI'];
if (ldapUrl) ldapArgs.push('-H', ldapUrl);
ldapArgs.push('-O', 'maxssf=256');
if (ldapmodify.code !== 0) {
const detail = String(ldapmodify.stderr || ldapmodify.stdout || '').trim();
throw new Error(`Ошибка установки пароля через Kerberos/GSSAPI (код ${ldapmodify.code})${detail ? ': ' + detail : ''}`);
const ldapmodify = await runChild(ldapmodifyExe, ldapArgs, {
env: ccacheEnv,
input: buildUnicodePwdLdif(userDN, password),
timeoutMs,
describe: 'ldapmodify'
});
if (ldapmodify.code !== 0) {
const detail = String(ldapmodify.stderr || ldapmodify.stdout || '').trim();
throw new Error(`Ошибка установки пароля через Kerberos/GSSAPI (код ${ldapmodify.code})${detail ? ': ' + detail : ''}`);
}
await destroyCache();
return;
} catch (err) {
lastError = err;
await destroyCache();
if (attempt < maxAttempts) {
console.warn(`⚠ Установка пароля через Kerberos: попытка ${attempt}/${maxAttempts} не удалась, повтор через ${retryDelayMs} мс: ${err.message}`);
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
}
throw lastError;
}
module.exports = {