ManageIQ/manageiq-appliance_console

View on GitHub
lib/manageiq/appliance_console/key_configuration.rb

Summary

Maintainability
A
1 hr
Test Coverage
B
89%
require 'pathname'
require 'fileutils'
require 'net/scp'
require 'active_support/all'
require 'manageiq-password'

require_relative './manageiq_user_mixin'

module ManageIQ
module ApplianceConsole
  CERT_DIR = ENV['KEY_ROOT'] || ManageIQ::ApplianceConsole::RAILS_ROOT.join("certs")
  KEY_FILE = "#{CERT_DIR}/v2_key".freeze
  NEW_KEY_FILE = "#{KEY_FILE}.tmp".freeze

  class KeyConfiguration
    include ManageIQ::ApplianceConsole::ManageiqUserMixin

    attr_accessor :host, :login, :password, :key_path, :action, :force

    def initialize(options = {})
      options.each { |k, v| public_send("#{k}=", v) }
      @action ||= :create
      @login ||= "root"
      @key_path ||= KEY_FILE
    end

    def ask_questions
      if key_exist?
        @force = agree("Overwrite existing encryption key (v2_key)? (Y/N): ")
        return false unless @force
      end

      @action = ask_for_action(@action)

      if fetch_key?
        say("")
        @host      = ask_for_ip_or_hostname("hostname for appliance with encryption key", @host)
        @login     = ask_for_string("appliance SSH login", @login)
        @password  = ask_for_password("appliance SSH password", @password)
        @key_path  = ask_for_string("path of remote encryption key", @key_path)
      end
      @action
    end

    def ask_question_loop
      loop do
        return false unless ask_questions
        return true if activate
        return false unless agree("Try again? (Y/N) ")
      end
    end

    def activate
      if !key_exist? || force
        if get_new_key
          save_new_key
        else
          remove_new_key_if_any
          false
        end
      else
        # probably only got here via the cli
        $stderr.puts
        $stderr.puts "Only generate one encryption key (v2_key) per installation."
        $stderr.puts "Chances are you did not want to overwrite this file."
        $stderr.puts "If you do this all encrypted secrets in the database will not be readable."
        $stderr.puts "Please backup your key and run this command again with --force-key."
        $stderr.puts
        false
      end
    end

    def save_new_key
      begin
        FileUtils.mv(NEW_KEY_FILE, KEY_FILE, :force => true)
      rescue => e
        say("Failed to overwrite original key, original key kept. #{e.message}")
        return false
      end
      FileUtils.chmod(0o400, KEY_FILE)
    end

    def remove_new_key_if_any
      FileUtils.rm(NEW_KEY_FILE) if File.exist?(NEW_KEY_FILE)
    end

    def key_exist?
      File.exist?(KEY_FILE)
    end

    def fetch_key?
      @action == :fetch
    end

    def create_key
      return unless !!ManageIQ::Password.generate_symmetric(NEW_KEY_FILE)

      File.chown(manageiq_uid, manageiq_gid, NEW_KEY_FILE)
    end

    def fetch_key
      # use :verbose => 1 (or :debug for later versions) to see actual errors
      Net::SCP.start(host, login, :password => password) do |scp|
        scp.download!(key_path, NEW_KEY_FILE)
      end
      File.chown(manageiq_uid, manageiq_gid, NEW_KEY_FILE)
      File.exist?(NEW_KEY_FILE)
    rescue => e
      say("Failed to fetch key: #{e.message}")
      false
    end

    private

    def ask_for_action(default_action)
      options = {'Create key' => :create, 'Fetch key from remote machine' => :fetch}
      ask_with_menu("Encryption Key", options, default_action, false)
    end

    def get_new_key
      fetch_key? ? fetch_key : create_key
    end
  end
end
end