Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smart proxies #146

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 10 additions & 9 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2021-06-09 10:26:01 UTC using RuboCop version 1.16.0.
# on 2021-07-28 22:19:11 UTC using RuboCop version 1.16.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -18,7 +18,7 @@ Lint/RequireParentheses:
Exclude:
- 'app/controllers/concerns/foreman_puppet/extensions/hosts_controller_extensions.rb'

# Offense count: 42
# Offense count: 44
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 41
Expand All @@ -34,17 +34,17 @@ Metrics/BlockLength:
Metrics/ClassLength:
Max: 250

# Offense count: 16
# Offense count: 18
# Configuration parameters: IgnoredMethods.
Metrics/CyclomaticComplexity:
Max: 17

# Offense count: 38
# Offense count: 39
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
Metrics/MethodLength:
Max: 23

# Offense count: 3
# Offense count: 4
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 171
Expand All @@ -54,16 +54,17 @@ Metrics/ModuleLength:
Metrics/ParameterLists:
MaxOptionalParameters: 4

# Offense count: 15
# Offense count: 17
# Configuration parameters: IgnoredMethods.
Metrics/PerceivedComplexity:
Max: 18

# Offense count: 1
# Offense count: 6
# Configuration parameters: EnforcedStyle, IgnoredPatterns.
# SupportedStyles: snake_case, camelCase
Naming/MethodName:
Exclude:
- 'app/models/concerns/foreman_puppet/orchestration/puppetca.rb'
- 'app/models/foreman_puppet/environment.rb'

# Offense count: 2
Expand Down Expand Up @@ -96,7 +97,7 @@ Rails/SkipsModelValidations:
Exclude:
- 'test/controllers/foreman_puppet/api/v2/environments_controller_test.rb'

# Offense count: 198
# Offense count: 213
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Expand Down Expand Up @@ -130,7 +131,7 @@ Style/SymbolProc:
Exclude:
- 'app/views/foreman_puppet/api/v2/import_puppetclasses/show.json.rabl'

# Offense count: 272
# Offense count: 279
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Expand Down
20 changes: 20 additions & 0 deletions app/models/concerns/foreman_puppet/extensions/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Host
included do
prepend PrependedMethods

include ForemanPuppet::Orchestration::Puppetca if SETTINGS[:unattended]

if ForemanPuppet.extracted_from_core?
has_one :environment, through: :puppet, class_name: 'ForemanPuppet::Environment'
else
Expand All @@ -16,6 +18,10 @@ module Host
host_classes_assoc&.instance_variable_set(:@class_name, 'ForemanPuppet::HostClass')
end

smart_proxy_reference self: %i[puppet_proxy_id puppet_ca_proxy_id]

before_save :check_puppet_ca_proxy_is_required?

include_in_clone puppet: %i[config_groups host_config_groups host_classes]

scoped_search relation: :environment, on: :name, complete_value: true, rename: :environment
Expand All @@ -25,6 +31,20 @@ module Host
ext_method: :search_by_puppetclass
scoped_search relation: :config_groups, on: :name, complete_value: true, rename: :config_group, only_explicit: true, operators: ['= ', '~ '],
ext_method: :search_by_config_group

apipie :class do
property :puppetca_token, 'Token::Puppetca', desc: 'Returns Puppet CA token for this host'
end

private

# fall back to our puppet proxy in case our puppet ca is not defined/used.
def check_puppet_ca_proxy_is_required?
return true if puppet_ca_proxy_id.present? || puppet_proxy_id.blank?
self.puppet_ca_proxy ||= puppet_proxy if puppet_proxy.has_feature?('Puppet CA')
rescue StandardError
true # we don't want to break anything, so just skipping.
end
end

class_methods do
Expand Down
112 changes: 112 additions & 0 deletions app/models/concerns/foreman_puppet/orchestration/puppetca.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
module ForemanPuppet
module Orchestration
module Puppetca
extend ActiveSupport::Concern
include ::Orchestration::Common

included do
attr_reader :puppetca

after_validation :initialize_puppetca, unless: :skip_orchestration?
after_validation :queue_puppetca
before_destroy :initialize_puppetca, :queue_puppetca_destroy
end

protected

def initialize_puppetca
return unless puppetca?
return unless Setting[:manage_puppetca]
@puppetca = ProxyAPI::Puppetca.new url: puppet_ca_proxy.url
true
rescue StandardError => e
failure _('Failed to initialize the PuppetCA proxy: %s') % e, e
end

# Removes the host's puppet certificate from the puppetmaster's CA
def delCertificate
logger.info "Remove puppet certificate for #{name}"
puppetca.del_certificate certname
end

# Empty method for rollbacks - maybe in the future we would support creating the certificates directly
def setCertificate
end

# Reset certname based on whether to use uuids or the hostname
def resetCertname
logger.info "Resetting certname for #{name}"
self.certname = Setting[:use_uuid_for_certificates] ? Foreman.uuid : hostname
end

# Adds the host's name to the autosign.conf file
def setAutosign
logger.info "Adding autosign entry for #{name}"
response = puppetca.set_autosign certname
# return if puppetca is using basic autosigning
return response if response.in? [true, false]
unless response.is_a?(Hash) && response['generated_token'].present?
logger.warn "Received an unexpected smart proxy response: #{response}"
return false
end
puppet.create_puppetca_token value: response['generated_token']
end

# Removes the host's name from the autosign.conf file
def delAutosign
logger.info "Delete the autosign entry for #{name}"
puppetca_token.destroy! if puppetca_token.present?
puppetca.del_autosign certname
end

private

def queue_puppetca
return log_orchestration_errors unless puppetca? && errors.empty?
return unless Setting[:manage_puppetca]
new_record? ? queue_puppetca_create : queue_puppetca_update
end

def queue_puppetca_certname_reset
post_queue.create(name: _('Reset PuppetCA certname for %s') % self, priority: 49,
action: [self, :resetCertname])
end

def queue_puppetca_create
post_queue.create(name: _('Cleanup PuppetCA certificates for %s') % self, priority: 51,
action: [self, :delCertificate])
post_queue.create(name: _('Enable PuppetCA autosigning for %s') % self, priority: 55,
action: [self, :setAutosign])
end

def queue_puppetca_update
if old.build? && !build?
# Host has been built --> remove auto sign
queue_puppetca_autosign_destroy
elsif !old.build? && build?
# Host was set to build mode
# If use_uuid_for_certificates is true, reuse the certname UUID value.
# If false, then reset the certname if it does not match the hostname.
queue_puppetca_certname_reset if Setting[:use_uuid_for_certificates] ? !Foreman.is_uuid?(certname) : certname != hostname
queue_puppetca_autosign_destroy
queue_puppetca_create
end
true
end

def queue_puppetca_destroy
return unless puppetca? && errors.empty?
return unless Setting[:manage_puppetca]
post_queue.create(name: _('Delete PuppetCA certificates for %s') % self, priority: 59,
action: [self, :delCertificate])
queue_puppetca_autosign_destroy
true
end

def queue_puppetca_autosign_destroy
post_queue.create(name: _('Disable PuppetCA autosigning for %s') % self, priority: 50,
action: [self, :delAutosign])
end
end
end
end
9 changes: 6 additions & 3 deletions app/models/foreman_puppet/host_puppet_facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ class HostPuppetFacet < ApplicationRecord
include Facets::Base
include ForemanPuppet::PuppetFacetCommon

has_one :puppetca_token, foreign_key: :host_id, dependent: :destroy, inverse_of: :host, class_name: 'ForemanPuppet::Token::Puppetca'
has_many :host_classes, dependent: :destroy, class_name: 'ForemanPuppet::HostClass'
has_many :puppetclasses, through: :host_classes

validates :environment_id, presence: true, unless: ->(facet) { facet.host.puppet_proxy_id.blank? }
validates :environment_id, presence: true, unless: ->(facet) { facet.puppet_proxy_id.blank? }

after_validation :ensure_puppet_associations
before_save :clear_puppetinfo, if: :environment_id_changed?
Expand All @@ -27,11 +28,13 @@ def self.populate_fields_from_facts(host, parser, type, source_proxy)

# if proxy authentication is enabled and we have no puppet proxy set and the upload came from puppet,
# use it as puppet proxy.
host.puppet_proxy ||= source_proxy
facet.puppet_proxy ||= source_proxy
end

def self.inherited_attributes(new_hostgroup, attributes)
{ 'environment_id' => new_hostgroup.puppet&.inherited_environment_id }.merge(attributes)
{ 'environment_id' => new_hostgroup.puppet&.inherited_environment_id,
'puppet_proxy_id' => new_hostgroup.puppet&.inherited_puppet_proxy_id,
'puppet_ca_proxy_id' => new_hostgroup.puppet&.inherited_puppet_ca_proxy_id }.merge(attributes)
end

def clear_puppetinfo
Expand Down
61 changes: 61 additions & 0 deletions app/models/foreman_puppet/puppet_facet_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,30 @@ module ForemanPuppet
module PuppetFacetCommon
extend ActiveSupport::Concern

include ::BelongsToProxies

included do
belongs_to :environment
has_many :host_config_groups, as: :host, dependent: :destroy
has_many :config_groups, through: :host_config_groups
has_many :config_group_classes, through: :config_groups
has_many :group_puppetclasses, through: :config_groups, source: :puppetclasses

belongs_to_proxy :puppet_proxy,
feature: N_('Puppet'),
label: N_('Puppet Proxy'),
description: N_('Use the Puppetserver configured on this Smart Proxy'),
api_description: N_('Puppet proxy ID')

belongs_to_proxy :puppet_ca_proxy,
feature: 'Puppet CA',
label: N_('Puppet CA Proxy'),
description: N_('Use the Puppetserver CA configured on this Smart Proxy'),
api_description: N_('Puppet CA proxy ID')

alias_method :all_puppetclasses, :classes

before_save :check_puppet_ca_proxy_is_required?
end

def parent_name
Expand Down Expand Up @@ -86,5 +102,50 @@ def available_puppetclasses
return ForemanPuppet::Puppetclass.all.authorized(:view_puppetclasses) if environment.blank?
environment.puppetclasses - parent_classes
end

def puppetca?
return false if respond_to?(:managed?) && !managed?
puppetca_exists?
end

def puppetca_exists?
!!(puppet_ca_proxy && puppet_ca_proxy.url.present?)
end

def puppet_server_uri
return unless puppet_proxy
url = puppet_proxy.setting('Puppet', 'puppet_url')
url ||= "https://#{puppet_proxy}:8140"
URI(url)
end

# The Puppet server FQDN or an empty string. Exposed as a provisioning macro
def puppet_server
puppet_server_uri.try(:host) || ''
end
alias_method :puppetmaster, :puppet_server

def puppet_ca_server_uri
return unless puppet_ca_proxy
url = puppet_ca_proxy.setting('Puppet CA', 'puppet_url')
url ||= "https://#{puppet_ca_proxy}:8140"
URI(url)
end

# The Puppet CA server FQDN or an empty string. Exposed as a provisioning
# macro.
def puppet_ca_server
puppet_ca_server_uri.try(:host) || ''
end

private

# fall back to our puppet proxy in case our puppet ca is not defined/used.
def check_puppet_ca_proxy_is_required?
return true if puppet_ca_proxy_id.present? || puppet_proxy_id.blank?
self.puppet_ca_proxy ||= puppet_proxy if puppet_proxy.has_feature?('Puppet CA')
rescue StandardError
true # we don't want to break anything, so just skipping.
end
end
end
7 changes: 7 additions & 0 deletions app/models/foreman_puppet/token/puppetca.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ForemanPuppet
module Token
class Puppetca < ::Token
validates :value, uniqueness: true
end
end
end
10 changes: 10 additions & 0 deletions db/migrate/20210427122719_add_puppet_ca_proxy_column.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class AddPuppetCaProxyColumn < ActiveRecord::Migration[6.0]
def change
change_table :host_puppet_facets do |t|
t.references :puppet_ca_proxy, foreign_key: { to_table: :smart_proxies }
end
change_table :hostgroup_puppet_facets do |t|
t.references :puppet_ca_proxy, foreign_key: { to_table: :smart_proxies }
end
end
end
5 changes: 3 additions & 2 deletions lib/foreman_puppet/register.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,14 @@
api_view list: 'foreman_puppet/api/v2/host_puppet_facets/base',
single: 'foreman_puppet/api/v2/host_puppet_facets/host_single'
template_compatibility_properties :environment, :environment_id, :environment_name,
:puppetclasses, :all_puppetclasses
:puppetclasses, :all_puppetclasses, :puppet_server, :puppet_ca_server
set_dependent_action :destroy
end
configure_hostgroup(ForemanPuppet::HostgroupPuppetFacet) do
api_view list: 'foreman_puppet/api/v2/hostgroup_puppet_facets/base',
single: 'foreman_puppet/api/v2/hostgroup_puppet_facets/hostgroup_single'
template_compatibility_properties :environment, :environment_id, :environment_name
template_compatibility_properties :environment, :environment_id, :environment_name,
:puppet_server, :puppet_ca_server, :puppetca_token
set_dependent_action :destroy
end
end
Expand Down