Documentationcurrent version
Help us improve the docs by fixing typos and proposing enhancements.

Nikita

Action "ssh.root"

Prepare the system to receive password-less root login with SSL/TLS keys.

Prior executing this handler, a user with appropriate sudo permissions must be created. The script will use those credentials to loggin and will try to become root with the "sudo" command. Use the "command" property if you must use a different command (such as "sudo su -").

Additionnally, it disables SELINUX which require a restart. The restart is handled by Masson and the installation procedure will continue as soon as an SSH connection is again available.

Example

const {$status} = await nikita.ssh.root({
  "username": "vagrant",
  "private_key_path": "/Users/wdavidw/.vagrant.d/insecure_private_key"
  "public_key_path": "~/.ssh/id_rsa.pub"
})
console.info(`Public key was updoaded for root user: ${$status}`)

Schema definitions

definitions =
  config:
    type: 'object'
    properties:
      'command':
        oneOf: [{type: 'string'}, {typeof: 'function'}]
      'host':
        type: 'string'
        # oneOf: [{format: 'ipv4'}, {format: 'hostname'}]
        default: 'root'
        description: '''
        Command used to become the root user on the remote server, for example
        `su -`.
        '''
      'password':
        type: 'string'
        description: '''
        Password of the user with sudo permissions to establish the SSH
        connection  if no private key is provided.
        '''
      'port':
        type: 'integer'
        default: 22
        description: '''
        '''
      'private_key':
        type: 'string'
        description: '''
        Private key of the user with sudo permissions to establish the SSH
        connection if `password` is not provided.
        '''
      'private_key_path':
        type: 'string'
        description: '''
        Local file location of the private key of the user with sudo
        permissions and used to establish the SSH connection if `password` and
        `private_key` are not provided.
        '''
      'public_key':
        oneOf: [{type: 'string'}, {instanceof: 'Buffer'}]
        description: '''
        Public key added to "authorized_keys" to enable the root user.
        '''
      'public_key_path':
        type: 'string'
        description: '''
        Local path to the public key added to "authorized_keys" to enable the
        root  user.
        '''
      'selinux':
        oneOf: [
          {type: 'string', enum: ['disabled', 'enforcing', 'permissive']},
          {type: 'boolean'}
        ]
        default: 'permissive'
        description: '''
        Username of the user with sudo permissions to establish the SSH
        connection.
        '''
      'username':
        type: 'string'
        description: '''
        Username of the user with sudo permissions to establish the SSH
        connection.
        '''

Handler

handler = ({metadata, config, tools: {log}}) ->
  config.host ?= config.ip
  # config.command ?= 'su -'
  config.username ?= null
  config.password ?= null
  config.selinux ?= false
  config.selinux = 'permissive' if config.selinux is true
  # Validation
  throw Error "Invalid option \"selinux\": #{config.selinux}" if config.selinux and config.selinux not in ['enforcing', 'permissive', 'disabled']
  rebooting = false
  # Read public key if option is a path
  if config.public_key_path and not config.public_key
    location = await utils.tilde.normalize config.public_key_path
    try
      {data: config.public_key} = await fs.readFile location, 'ascii'
    catch err
      throw Error "Private key doesnt exists: #{JSON.stringify location}" if err.code is 'ENOENT'
      throw err
  # Read private key if option is a path
  if config.private_key_path and not config.private_key
    log message: "Read Private Key: #{JSON.stringify config.private_key_path}", level: 'DEBUG'
    location = await utils.tilde.normalize config.private_key_path
    try
      {data: config.private_key} = await fs.readFile location, 'ascii'
    catch err
      throw Error "Private key doesnt exists: #{JSON.stringify location}" if err.code is 'ENOENT'
      throw err
  await @call ->
    log message: "Connecting", level: 'DEBUG'
    conn = unless metadata.dry
    then await connect config
    else null
    log message: "Connected", level: 'INFO'
    command = []
    command.push """
    sed -i.back 's/.*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config;
    """
    command.push """
    mkdir -p /root/.ssh; chmod 700 /root/.ssh;
    echo '#{config.public_key}' >> /root/.ssh/authorized_keys;
    """ if config.public_key
    command.push """
    sed -i.back 's/.*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config;
    selinux="#{config.selinux or ''}";
    if [ -n "$selinux" ] && [ -f /etc/selinux/config ] && grep ^SELINUX="$selinux" /etc/selinux/config;
    then
      sed -i.back "s/^SELINUX=enforcing/SELINUX=$selinux/" /etc/selinux/config;
      ( reboot )&
      exit 2;
    fi;
    """
    command = command.join '\n'
    if config.username isnt 'root'
      command = command.replace /\n/g, ' '
      if typeof config.command is 'function'
        command = config.command command
      else if typeof config.command is 'string'
        command = "#{config.command} #{command}"
      else
        config.command = 'sudo '
        config.command += "-u #{config.user} " if config.user
        config.command = "echo -e \"#{config.password}\\n\" | #{config.command} -S " if config.password
        config.command += "-- sh -c \"#{command}\""
        command = config.command
    log message: "Enable Root Access", level: 'DEBUG'
    log message: command, type: 'stdin'
    unless metadata.dry
      child = exec
        ssh: conn
        command: command
      , (err) =>
        if err?.code is 2
          log message: "Root Access Enabled", level: 'WARN'
          err = null
          rebooting = true
        else throw err
      child.stdout.on 'data', (data) =>
        log message: data, type: 'stdout'
      child.stdout.on 'end', (data) =>
        log message: null, type: 'stdout'
      child.stderr.on 'data', (data) =>
        log message: data, type: 'stderr'
      child.stderr.on 'end', (data) =>
        log message: null, type: 'stderr'
  await @call $if: rebooting, $retry: true, $sleep: 3000, ->
    conn = await connect config
    conn.end()

Exports

module.exports =
  handler: handler
  metadata:
    definitions: definitions

Dependencies

fs = require('fs').promises
connect = require 'ssh2-connect'
exec = require 'ssh2-exec'
utils = require '../../utils'
Edit on GitHub
Navigate
About

Nikita is an open source project hosted on GitHub and developed by Adaltas.