Skip to content

Commit 3a29a7b

Browse files
committed
Fixes #38478 - Introduce SSH CA certificate support
1 parent e4147ae commit 3a29a7b

File tree

7 files changed

+69
-4
lines changed

7 files changed

+69
-4
lines changed

app/lib/proxy_api/remote_execution_ssh.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ def pubkey
1111
raise ProxyException.new(url, e, N_('Unable to fetch public key'))
1212
end
1313

14+
def ca_pubkey
15+
get('ca_pubkey')&.strip
16+
rescue => e
17+
raise ProxyException.new(url, e, N_('Unable to fetch CA public key'))
18+
end
19+
1420
def drop_from_known_hosts(hostname)
1521
delete('known_hosts/' + hostname)
1622
rescue => e

app/models/concerns/foreman_remote_execution/host_extensions.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ def remote_execution_proxies(provider, authorized = true)
108108
end
109109

110110
def remote_execution_ssh_keys
111-
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey }.compact.uniq
111+
remote_execution_keys(:pubkey)
112+
end
113+
114+
def remote_execution_ssh_ca_keys
115+
remote_execution_keys(:ca_pubkey)
112116
end
113117

114118
def drop_execution_interface_cache
@@ -139,10 +143,13 @@ def infrastructure_host?
139143

140144
def extend_host_params_hash(params)
141145
keys = remote_execution_ssh_keys
146+
ca_keys = remote_execution_ssh_ca_keys
142147
source = 'global'
143-
if keys.present?
144-
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
145-
params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
148+
{keys: keys, ca_keys: ca_keys}.each do |key_set_name, key_set|
149+
if key_set.present?
150+
value, safe_value = params.fetch("remote_execution_ssh_#{key_set_name}", {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
151+
params["remote_execution_ssh_#{key_set_name}"] = {:value => value + key_set, :safe_value => safe_value + key_set, :source => source}
152+
end
146153
end
147154
[:remote_execution_ssh_user, :remote_execution_effective_user_method,
148155
:remote_execution_connect_by_ip].each do |key|
@@ -156,6 +163,10 @@ def build_required_interfaces(attrs = {})
156163
super(attrs)
157164
self.primary_interface.execution = true if self.execution_interface.nil?
158165
end
166+
167+
def remote_execution_keys(retrieve_method)
168+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.public_send(retrieve_method) }.compact.uniq
169+
end
159170
end
160171
end
161172

app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ def pubkey
1111
self[:pubkey] || update_pubkey
1212
end
1313

14+
def ca_pubkey
15+
self[:ca_pubkey] || update_ca_pubkey
16+
end
17+
1418
def update_pubkey
1519
return unless has_feature?(%w(SSH Script))
1620

@@ -19,13 +23,23 @@ def update_pubkey
1923
key
2024
end
2125

26+
def update_ca_pubkey
27+
return unless has_feature?(%w(SSH Script))
28+
29+
# smart proxy is not required to have a CA pubkey, in which case an empty string is returned
30+
key = ::ProxyAPI::RemoteExecutionSSH.new(:url => url).ca_pubkey&.presence
31+
self.update_attribute(:ca_pubkey, key)
32+
key
33+
end
34+
2235
def drop_host_from_known_hosts(host)
2336
::ProxyAPI::RemoteExecutionSSH.new(:url => url).drop_from_known_hosts(host)
2437
end
2538

2639
def refresh
2740
errors = super
2841
update_pubkey
42+
update_ca_pubkey
2943
errors
3044
end
3145
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
attribute :ca_pubkey => :remote_execution_ca_pubkey
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddCAPubKeyToSmartProxy < ActiveRecord::Migration[7.0]
2+
def change
3+
add_column :smart_proxies, :ca_pubkey, :text
4+
end
5+
end

lib/foreman_remote_execution/plugin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
extend_template_helpers ForemanRemoteExecution::RendererMethods
220220

221221
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
222+
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/ca_pubkey'
222223
extend_rabl_template 'api/v2/interfaces/main', 'api/v2/interfaces/execution_flag'
223224
extend_rabl_template 'api/v2/subnets/show', 'api/v2/subnets/remote_execution_proxies'
224225
extend_rabl_template 'api/v2/hosts/main', 'api/v2/host/main'

test/unit/concerns/host_extensions_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
99
describe 'ssh specific params' do
1010
let(:host) { FactoryBot.create(:host, :with_execution) }
1111
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ [email protected]' }
12+
let(:ca_sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJE [email protected]' }
1213

1314
before do
1415
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
16+
SmartProxy.any_instance.stubs(:ca_pubkey).returns(ca_sshkey)
1517
Setting[:remote_execution_ssh_user] = 'root'
1618
Setting[:remote_execution_effective_user_method] = 'sudo'
1719
end
@@ -59,6 +61,31 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
5961
User.current = nil
6062
assert_includes host.remote_execution_ssh_keys, sshkey
6163
end
64+
65+
it 'has CA ssh keys in the parameters' do
66+
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
67+
end
68+
69+
it 'merges ssh CA keys from host parameters and proxies' do
70+
key = 'ssh-rsa not-even-a-key [email protected]'
71+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => [key])
72+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
73+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
74+
end
75+
76+
it 'merges ssh CA key as a string from host parameters and proxies' do
77+
key = 'ssh-rsa not-even-a-key [email protected]'
78+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => key)
79+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
80+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
81+
end
82+
83+
it 'has ssh CA keys in the parameters even when no user specified' do
84+
FactoryBot.create(:smart_proxy, :ssh)
85+
host.interfaces.first.subnet.remote_execution_proxies.clear
86+
User.current = nil
87+
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
88+
end
6289
end
6390

6491
context 'host has multiple nics' do

0 commit comments

Comments
 (0)