Ctrl K
ComunidadGuías…

lib/os-user-diagnostics.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Best-effort, read-only diagnostics for the current OS-user compatibility mode.

var fs = require("fs");
var childProcess = require("child_process");
var path = require("path");
var execFileSync = childProcess.execFileSync;
var osUsers = require("./os-users");

function commandAvailable(command, execFile) {
  try {
    execFile(command, ["--help"], { stdio: "ignore", timeout: 5000 });
    return true;
  } catch (e) {
    return false;
  }
}

function credentialPaths(home) {
  if (!home) return [];
  return require("./yoke/maintenance").credentialPaths(home).concat([
    home + "/.config/gh",
    home + "/.config/gcloud",
  ]);
}

function accessStatus(checks) {
  var names = ["read", "traverse", "write"];
  for (var i = 0; i < names.length; i++) {
    if (!checks[names[i]] || checks[names[i]].status === "unknown") return "unknown";
  }
  for (var j = 0; j < names.length; j++) {
    if (checks[names[j]].status === "deny") return "deny";
  }
  return "allow";
}

function unknownChecks() {
  return {
    read: { status: "unknown" },
    traverse: { status: "unknown" },
    write: { status: "unknown" },
  };
}

function projectAccessScript(projectPath) {
  return [
    "var fs = require('fs');",
    "var target = " + JSON.stringify(projectPath) + ";",
    "var checks = {};",
    "function check(name, mode) {",
    "  try { fs.accessSync(target, mode); checks[name] = { status: 'allow' }; }",
    "  catch (error) { checks[name] = { status: 'deny', code: String(error && error.code || 'EACCES') }; }",
    "}",
    "check('read', fs.constants.R_OK);",
    "check('traverse', fs.constants.X_OK);",
    "check('write', fs.constants.W_OK);",
    "process.stdout.write(JSON.stringify(checks));",
  ].join(" ");
}

/**
 * Check directory access as the mapped identity without creating or changing
 * any file. The spawn wrapper intentionally follows the runtime's current
 * supplementary-group behavior, including its setpriv fallback.
 */
function probeMappedProjectAccess(project, user, options) {
  options = options || {};
  var result = {
    projectSlug: project && project.slug || null,
    userId: user && user.id || null,
    result: "unknown",
    checks: unknownChecks(),
  };
  if (!project || !project.path) {
    result.evidence = "project_path_unavailable";
    return result;
  }
  if (!user || !user.linuxUser) {
    result.evidence = "linux_mapping_missing";
    return result;
  }

  var resolveUser = options.resolveUser || osUsers.resolveOsUserInfo;
  var wrapSpawn = options.wrapSpawn || osUsers.wrapSpawnAsUser;
  var execFile = options.execFile || execFileSync;
  var nodePath = options.nodePath || process.execPath;
  var account;
  try {
    account = resolveUser(user.linuxUser);
  } catch (e) {
    result.evidence = "linux_account_unavailable";
    return result;
  }
  if (!account || account.uid == null || account.gid == null) {
    result.evidence = "linux_account_invalid";
    return result;
  }

  try {
    var spawn = wrapSpawn(nodePath, ["-e", projectAccessScript(project.path)], {
      encoding: "utf8",
      timeout: 5000,
      stdio: "pipe",
      uid: account.uid,
      gid: account.gid,
    }, options.setprivCheck);
    var output = execFile(spawn.command, spawn.args, spawn.options);
    var checks = JSON.parse(String(output).trim());
    if (!checks || typeof checks !== "object") throw new Error("Invalid access probe result");
    result.checks = checks;
    result.result = accessStatus(checks);
    result.evidence = "mapped_identity_access";
  } catch (e) {
    result.evidence = "probe_unavailable";
  }
  return result;
}

function collectUserDiagnostics(user, options) {
  options = options || {};
  var resolveUser = options.resolveUser || osUsers.resolveOsUserInfo;
  var exists = options.exists || fs.existsSync;
  var execFile = options.execFile || execFileSync;
  var result = {
    userId: user && user.id || null,
    linuxUser: user && user.linuxUser || null,
    mapping: "missing",
    account: "unknown",
    supplementaryGroups: "unknown",
    credentialPaths: [],
  };
  if (!user || !user.linuxUser) return result;

  var account;
  try {
    account = resolveUser(user.linuxUser);
    result.mapping = "resolved";
    result.account = "present";
  } catch (e) {
    result.mapping = "unavailable";
    result.account = "missing";
    result.evidence = "linux_account_unavailable";
    return result;
  }

  try {
    var output = execFile("id", ["-Gn", user.linuxUser], { encoding: "utf8", timeout: 5000, stdio: "pipe" });
    result.supplementaryGroups = output.trim() ? output.trim().split(/\s+/) : [];
  } catch (e) {
    result.supplementaryGroups = "unknown";
  }
  var paths = credentialPaths(account.home);
  for (var i = 0; i < paths.length; i++) {
    var present = "unknown";
    try { present = !!exists(paths[i]); } catch (e) {}
    result.credentialPaths.push({ path: paths[i], present: present });
  }
  return result;
}

function collectOsUserDiagnostics(options) {
  options = options || {};
  var users = options.users || [];
  var projects = options.projects || [];
  var probeProjectAccess = options.probeProjectAccess || probeMappedProjectAccess;
  var execFile = options.execFile || execFileSync;
  var result = {
    collectedAt: Date.now(),
    setprivAvailable: commandAvailable("setpriv", execFile),
    users: [],
    projectAccess: [],
  };
  for (var i = 0; i < users.length; i++) {
    try {
      result.users.push(collectUserDiagnostics(users[i], options));
    } catch (e) {
      result.users.push({
        userId: users[i] && users[i].id || null,
        linuxUser: users[i] && users[i].linuxUser || null,
        mapping: "unknown",
        account: "unknown",
        supplementaryGroups: "unknown",
        credentialPaths: [],
      });
    }
  }
  for (var pi = 0; pi < projects.length; pi++) {
    var project = projects[pi];
    for (var ui = 0; ui < users.length; ui++) {
      var user = users[ui];
      if (!project || !user) continue;
      var probe;
      try {
        probe = probeProjectAccess(project, user, options);
        if (!probe || typeof probe !== "object") probe = { result: "unknown", evidence: "invalid_probe_result" };
      } catch (e) {
        probe = { result: "unknown", evidence: "probe_failed" };
      }
      if (!probe.projectSlug) probe.projectSlug = project.slug || null;
      if (!probe.userId) probe.userId = user.id || null;
      if (!probe.result) probe.result = "unknown";
      result.projectAccess.push(probe);
    }
  }
  return result;
}

function summarizeDiagnostics(result) {
  var users = result.users || [];
  var projectAccess = result.projectAccess || [];
  var unavailableMappings = 0;
  var unknownProbes = 0;
  for (var i = 0; i < users.length; i++) {
    if (users[i].mapping !== "resolved") unavailableMappings++;
  }
  for (var j = 0; j < projectAccess.length; j++) {
    if (projectAccess[j].result === "unknown") unknownProbes++;
  }
  return "OS-user diagnostics: " + users.length + " user mappings, " + unavailableMappings + " unavailable, " + projectAccess.length + " project probes, " + unknownProbes + " unavailable";
}

function diagnosticsPayload(options) {
  var users = typeof options.getUsers === "function" ? options.getUsers() : options.users || [];
  var projects = typeof options.getProjects === "function" ? options.getProjects() : options.projects || [];
  return {
    users: users.map(function(user) {
      return { id: user && user.id || null, linuxUser: user && user.linuxUser || null };
    }),
    projects: projects.map(function(project) {
      return { slug: project && project.slug || null, path: project && project.path || null };
    }),
  };
}

function logDiagnosticMessage(logger, level, message) {
  try {
    if (logger && typeof logger[level] === "function") logger[level]("[daemon] " + message);
  } catch (e) {}
}

/**
 * Run blocking host probes in a short-lived child so diagnostics cannot stall
 * the daemon event loop after it starts serving requests.
 */
function scheduleOsUserDiagnosticsAsync(options) {
  options = options || {};
  var schedule = options.schedule || setTimeout;
  var clearSchedule = options.clearSchedule || clearTimeout;
  var spawn = options.spawn || childProcess.spawn;
  var logger = options.logger || console;
  var timeoutMs = options.timeoutMs || 30000;
  try {
    schedule(function() {
      var child;
      var timeout;
      var output = "";
      var completed = false;
      function finish(level, message) {
        if (completed) return;
        completed = true;
        if (timeout) clearSchedule(timeout);
        logDiagnosticMessage(logger, level, message);
      }
      try {
        child = spawn(options.nodePath || process.execPath, [path.join(__dirname, "os-user-diagnostics-worker.js")], {
          stdio: ["pipe", "pipe", "ignore"],
        });
        if (!child || !child.stdin || !child.stdout || typeof child.once !== "function") {
          finish("warn", "OS-user diagnostics unavailable");
          return;
        }
        child.stdout.on("data", function(chunk) { output += String(chunk); });
        child.once("error", function() { finish("warn", "OS-user diagnostics unavailable"); });
        child.once("close", function(code) {
          if (completed) return;
          if (code !== 0) {
            finish("warn", "OS-user diagnostics unavailable");
            return;
          }
          try {
            var report = JSON.parse(output);
            if (!report || typeof report.summary !== "string") throw new Error("Invalid diagnostics report");
            finish("log", report.summary);
          } catch (e) {
            finish("warn", "OS-user diagnostics unavailable");
          }
        });
        timeout = schedule(function() {
          try { child.kill(); } catch (e) {}
          finish("warn", "OS-user diagnostics timed out");
        }, timeoutMs);
        child.stdin.end(JSON.stringify(diagnosticsPayload(options)));
      } catch (e) {
        try { if (child && typeof child.kill === "function") child.kill(); } catch (killError) {}
        finish("warn", "OS-user diagnostics unavailable");
      }
    }, 0);
  } catch (e) {
    logDiagnosticMessage(logger, "warn", "OS-user diagnostics could not be scheduled");
  }
}

module.exports = {
  collectOsUserDiagnostics: collectOsUserDiagnostics,
  collectUserDiagnostics: collectUserDiagnostics,
  credentialPaths: credentialPaths,
  probeMappedProjectAccess: probeMappedProjectAccess,
  scheduleOsUserDiagnosticsAsync: scheduleOsUserDiagnosticsAsync,
  summarizeDiagnostics: summarizeDiagnostics,
};