Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/lib/proxy_api/remote_execution_ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ def pubkey
raise ProxyException.new(url, e, N_('Unable to fetch public key'))
end

def ca_pubkey
get('ca_pubkey')&.strip
rescue => e
raise ProxyException.new(url, e, N_('Unable to fetch CA public key'))
end

def drop_from_known_hosts(hostname)
delete('known_hosts/' + hostname)
rescue => e
Expand Down
16 changes: 12 additions & 4 deletions app/models/concerns/foreman_remote_execution/host_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ def remote_execution_proxies(provider, authorized = true)
end

def remote_execution_ssh_keys
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
# only include public keys from SSH proxies that don't have SSH cert verification configured
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey if proxy.ca_pubkey.blank? }.compact.uniq
end

def remote_execution_ssh_ca_keys
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey }.compact.uniq
end

def drop_execution_interface_cache
Expand Down Expand Up @@ -139,10 +144,13 @@ def infrastructure_host?

def extend_host_params_hash(params)
keys = remote_execution_ssh_keys
ca_keys = remote_execution_ssh_ca_keys
source = 'global'
if keys.present?
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
{keys: keys, ca_keys: ca_keys}.each do |key_set_name, key_set|
if key_set.present?
value, safe_value = params.fetch("remote_execution_ssh_#{key_set_name}", {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
params["remote_execution_ssh_#{key_set_name}"] = {:value => value + key_set, :safe_value => safe_value + key_set, :source => source}
end
end
[:remote_execution_ssh_user, :remote_execution_effective_user_method,
:remote_execution_connect_by_ip].each do |key|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def pubkey
self[:pubkey] || update_pubkey
end

def ca_pubkey
self[:ca_pubkey] || update_ca_pubkey
end

def update_pubkey
return unless has_feature?(%w(SSH Script))

Expand All @@ -19,13 +23,23 @@ def update_pubkey
key
end

def update_ca_pubkey
return unless has_feature?(%w(SSH Script))

# smart proxy is not required to have a CA pubkey, in which case an empty string is returned
key = ::ProxyAPI::RemoteExecutionSSH.new(:url => url).ca_pubkey&.presence
self.update_attribute(:ca_pubkey, key)
key
end

def drop_host_from_known_hosts(host)
::ProxyAPI::RemoteExecutionSSH.new(:url => url).drop_from_known_hosts(host)
end

def refresh
errors = super
update_pubkey
update_ca_pubkey
errors
end
end
Expand Down
1 change: 1 addition & 0 deletions app/views/api/v2/smart_proxies/ca_pubkey.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
attribute :ca_pubkey => :remote_execution_ca_pubkey
5 changes: 5 additions & 0 deletions db/migrate/20250606125543_add_ca_pub_key_to_smart_proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddCAPubKeyToSmartProxy < ActiveRecord::Migration[7.0]
def change
add_column :smart_proxies, :ca_pubkey, :text
end
end
1 change: 1 addition & 0 deletions lib/foreman_remote_execution/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
extend_template_helpers ForemanRemoteExecution::RendererMethods

extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/ca_pubkey'
extend_rabl_template 'api/v2/interfaces/main', 'api/v2/interfaces/execution_flag'
extend_rabl_template 'api/v2/subnets/show', 'api/v2/subnets/remote_execution_proxies'
extend_rabl_template 'api/v2/hosts/main', 'api/v2/host/main'
Expand Down
43 changes: 43 additions & 0 deletions test/unit/concerns/host_extensions_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase

before do
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
SmartProxy.any_instance.stubs(:ca_pubkey).returns(nil)
Setting[:remote_execution_ssh_user] = 'root'
Setting[:remote_execution_effective_user_method] = 'sudo'
end
Expand Down Expand Up @@ -61,6 +62,48 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
end
end

describe 'has ssh CA key configured' do
let(:host) { FactoryBot.create(:host, :with_execution) }
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ [email protected]' }
let(:ca_sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJE [email protected]' }

before do
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
SmartProxy.any_instance.stubs(:ca_pubkey).returns(ca_sshkey)
Setting[:remote_execution_ssh_user] = 'root'
Setting[:remote_execution_effective_user_method] = 'sudo'
end

it 'has CA ssh keys in the parameters' do
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
end

it 'excludes ssh keys from proxies that have SSH CA key configured' do
assert_empty host.remote_execution_ssh_keys
end

it 'merges ssh CA keys from host parameters and proxies' do
key = 'ssh-rsa not-even-a-key [email protected]'
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => [key])
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
end

it 'has ssh CA keys in the parameters even when no user specified' do
FactoryBot.create(:smart_proxy, :ssh)
host.interfaces.first.subnet.remote_execution_proxies.clear
User.current = nil
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
end

it 'merges ssh CA key as a string from host parameters and proxies' do
key = 'ssh-rsa not-even-a-key [email protected]'
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => key)
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
end
end

context 'host has multiple nics' do
let(:host) { FactoryBot.build(:host, :with_execution) }

Expand Down