kshetline
Posts: 68
Joined: Thu Jun 07, 2018 3:17 am

polkit "Interactive authentication required" errors now blocking once-working installation script

Thu May 13, 2021 9:35 am

I created an automated installation script in TypeScript (that compiles to JavaScript and runs under Node.js) for a project of mine, a script that used to work quite well, but which is now failing with "Interactive authentication required" errors which come from polkit, which is apparently a security policy service used by Raspbian.

The script is launched using sudo, so it should be running with full admin privileges for the things which are failing, namely stopping my own service (service weatherService stop), and creating or modifying the files in /etc/default/ and /etc/init.d/ which define that service.

All I can guess is that some recent security update it causing me grief, but what that might be in particular, I have no idea. When I google for the specific error message I'm getting, and about polkit, most discussion seems to center around which users are sudo-ers, and what sudo-ers can do. But everything I'm trying to do as the "pi" user, while running with "sudo", is stuff that I can still do in a terminal window without any problems, and without any "Interactive authentication" popping up forcing me to authenticate anything.

The code is way too big to post here, but can be found on Github: https://github.com/kshetline/aw-clock/

...in particular in this file: https://github.com/kshetline/aw-clock/b ... r/build.ts

Here are some key snippets:

Code: Select all

        try {
          await monitorProcess(spawn('service', ['weatherService', 'stop']), spin, ErrorMode.ANY_ERROR);
        }
        catch (err) {
          const msg = err.message || err.toString();

          // Grief from polkit?
          if (/Interactive authentication required/i.test(msg)) {
             // Blah, blah, blah...
          }
        }

import { ChildProcess, execSync, spawn as nodeSpawn } from 'child_process';


export function spawn(command: string, args: string[], options?: any): ChildProcess;
export function spawn(command: string, uid?: number, args?: string[], options?: any): ChildProcess;
export function spawn(command: string, uidOrArgs?: string[] | number, optionsOrArgs?: any, options?: any): ChildProcess {
  let uid: number;
  let args: string[];

  if (isNumber(uidOrArgs)) {
    uid = uidOrArgs;
    args = optionsOrArgs || [];
  }
  else {
    args = uidOrArgs || [];
    options = optionsOrArgs;
    uid = options?.uid;
  }

  if (uid != null) {
    options = options ?? {};
    options.uid = uid;

    if (!options.env) {
      options.env = {};
      Object.assign(options.env, process.env);
    }

    options.env.HOME = userHome;
    options.env.LOGNAME = sudoUser;
    options.env.npm_config_cache = userHome + '/.npm';
    options.env.USER = sudoUser;
  }

  if (isWindows) {
    if (/^(chmod|chown|id)$/.test(command)) {
      // Effectively a "noop"
      command = 'rundll32';
      args = [];
    }
    else if (command === 'rm') {
      // Ad hoc, not a general solution conversion of rm!
      command = 'rmdir';
      args = ['/S', '/Q', args[1].replace(/\//g, '\\')];
    }
    else if (command === 'which')
      command = 'where';

    const cmd = process.env.comspec || 'cmd';

    if (options?.uid != null) {
      options = Object.assign({}, options);
      delete options.uid;
    }

    return nodeSpawn(cmd, ['/c', command, ...args], options);
  }
  else
    return nodeSpawn(command, args, options);
}

export function monitorProcess(proc: ChildProcess, markTime: () => void = undefined, errorMode = ErrorMode.DEFAULT): Promise<string> {
  let errors = '';
  let output = '';

  return new Promise<string>((resolve, reject) => {
    const slowSpin = unref(setInterval(markTime || NO_OP, MAX_MARK_TIME_DELAY));

    proc.stderr.on('data', data => {
      (markTime || NO_OP)();
      data = stripFormatting(data.toString());

      // This gets confusing, because a lot of non-error progress messaging goes to stderr, and the
      //   webpack process doesn't exit with an error for compilation errors unless you make it do so.
      if (/(\[webpack.Progress])|Warning\b/.test(data))
        return;

      errors += data;
    });
    proc.stdout.on('data', data => {
      (markTime || NO_OP)();
      data = data.toString();
      output += data;

      if (errorish(data))
        errors = errors ? errors + '\n' + data : data;
    });
    proc.on('error', err => {
      clearInterval(slowSpin);

      if (errorMode !== ErrorMode.NO_ERRORS)
        resolve(output);
      else
        reject(err);
    });
    proc.on('close', () => {
      clearInterval(slowSpin);

      if (errorMode !== ErrorMode.NO_ERRORS && errors && (errorMode === ErrorMode.ANY_ERROR || errorish(errors)))
        reject(errors.replace(/\bE:\s+/g, '').trim());
      else
        resolve(output);
    });
  });
}
Despite all of the complication of the above code, meant for monitoring process output streams, handling different OSes, and setting up some environment variables, it's really just a wrapper around Node.js's spawn function, and the fact that commands that I used to be able to spawn successfully now fail with this polkit problem.

The polkit service appears to be a giant can of worms, with many separate complicated policy files defining who can do what with what. Since all I'm trying to do with my script is do things a sudo-er normally has the privileges to do, I don't even know why polkit cares, or what this "Interactive authentication" even looks like -- it's not as if some prompt is appearing that the user or my script could try to interact with anyway.

What I'm really hoping for (with low expectations of success) is that someone reading this will have an "Oh, THAT problem!" reaction. Short of that, this glitch is going to be really tough to describe well enough to anyone not already familiar with a similar problem.

kshetline
Posts: 68
Joined: Thu Jun 07, 2018 3:17 am

Re: polkit "Interactive authentication required" errors now blocking once-working installation script

Sat May 15, 2021 11:37 pm

I never found out exactly why this issue cropped up, but I at least have a work-around and a general idea where things went wrong.

My build script was written in TypeScript, and, rather than build a JavaScript version out of that, I ran the TypeScript directly using ts-node (which handles the conversion to JavaScript internally). Further, the invocation of ts-node was indirect, run as an npm script within my package.json file.

All of this meant that, at the end of a shell script, I kicked off the TypeScript installer like this:

Code: Select all

npm run build:prod -- $path --bash $*
build:prod was declared as this:

Code: Select all

"build:prod": "ts-node build.ts -p",
Even though the above used to work without a hitch, now, after something changed, either in npm passing execution off to ts-node, or between ts-node compiling TypeScript, then executing JavaScript, sudo privileges now get lost.

I had to pre-build my TypeScript as JavaScript with webpack, and skip invoking the npm script, instead executing the same commands that script executes:

Code: Select all

node build.js -p $path --bash $*
This more direct invocation of my installer avoids whatever issue caused the polkit error.

Return to “Other programming languages”