ManageIQ/manageiq-appliance_console

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

Summary

Maintainability
A
2 hrs
Test Coverage
A
100%
require "awesome_spawn"
require "linux_admin"
require "fileutils"

module ManageIQ
module ApplianceConsole
  class Certificate
    STATUS_COMPLETE = :complete

    # map `getcert status` return codes to something more descriptive
    # 0 => :complete -- keys/certs generated
    # 1 => :no_key   -- either certmonger is down, or we havent asked for the key yet. (assuming the latter)
    # 2 => :rejected -- request failed. we need to resubmit once we fix stuff
    # 3 => :waiting  -- couldn't contact CA, will try again
    # 4 => :error    -- certmonger is not configured properly
    # 5 => :waiting  -- waiting for CA to send back the certificate
    STATUS_RETURN_CODES = [:complete, :no_key, :rejected, :waiting, :error, :waiting]

    # key filename defaults to certificate name w/ different extension
    attr_writer   :key_filename
    attr_accessor :cert_filename
    # root certificate filename
    attr_accessor :root_filename
    attr_accessor :service
    # 509 v3 extesions for stuff to signify purpose of this certificate (e.g.: client)
    attr_accessor :extensions
    attr_accessor :owner

    # hostname of current machine
    attr_accessor :hostname
    # ipa realm
    attr_accessor :realm
    # name of certificate authority
    attr_accessor :ca_name

    def initialize(options = {})
      options.each { |n, v| public_send("#{n}=", v) }
      @ca_name ||= "ipa"
      @extensions ||= %w(server client)
      @realm ||= hostname.split(".")[1..-1].join(".").upcase if hostname
    end

    def request
      undo_tracking if complete?

      if should_request_key?
        principal.register
        remove_key_pair
        request_certificate
        # NOTE: status probably changed
        set_owner_of_key unless rejected?
      end

      if complete?
        make_certs_world_readable
        yield if block_given?
      end
      self
    end

    def principal
      @principal ||= Principal.new(:hostname => hostname, :realm => realm, :service => service, :ca_name => ca_name)
    end

    def request_certificate
      if rejected?
        request_again
      else
        request_first
      end
      clear_status
    end

    # workaround
    # currently, the -C is not run after the root certificate is written
    def make_certs_world_readable
      FileUtils.chmod(0644, [root_filename, cert_filename].compact)
    end

    def set_owner_of_key
      FileUtils.chown(owner.split(".").first, owner.split(".")[1], key_filename) if owner && (owner != "root")
      self
    end

    # statuses

    def should_request_key?
      no_key? || rejected?
    end

    def no_key?
      status == :no_key
    end

    def rejected?
      status == :rejected
    end

    def complete?
      status == :complete
    end

    def clear_status
      @status = nil
    end

    def status
      @status ||= key_status
    end

    def enable_certmonger
      say("enabling certmonger to start on reboot")
      LinuxAdmin::Service.new("certmonger").enable.start
    end

    private

    def remove_key_pair
      FileUtils.rm_f(cert_filename) if File.exist?(cert_filename)
      FileUtils.rm_f(key_filename)  if File.exist?(key_filename)
    end

    def undo_tracking
      stop_tracking
      FileUtils.rm_f(root_filename) if File.exist?(root_filename)
      remove_key_pair
      clear_status
    end

    def stop_tracking
      AwesomeSpawn.run!("/usr/bin/getcert", :params => ["stop-tracking", "-f", cert_filename, "-k", key_filename])
    end

    def request_first
      params = {
        nil  => "request",
        "-c" => ca_name,
        "-v" => nil, # verbose
        "-w" => nil, # wait til completion if possible
        "-k" => key_filename,
        "-f" => cert_filename,
        "-N" => principal.subject_name,
        "-K" => principal.name,
        "-C" => "chmod 644 #{cert_filename} #{root_filename}",
        "-U" => key_ext_usage
      }
      params["-F"] = root_filename if root_filename

      AwesomeSpawn.run!("/usr/bin/getcert", :params => params)
      self
    end

    def request_again
      AwesomeSpawn.run!("/usr/bin/getcert", :params => ["resubmit", "-w", "-f", cert_filename])
      self
    end

    def key_filename
      @key_filename || "#{cert_filename.chomp(File.extname(cert_filename))}.key"
    end

    def key_status
      ret = AwesomeSpawn.run("/usr/bin/getcert", :params => ["status", "-f", cert_filename])
      STATUS_RETURN_CODES[ret.exit_status]
    end

    def key_ext_usage
      extensions.collect { |n| "id-kp-#{n}Auth" }.join(",")
    end
  end
end
end