Skip to content

Commit f01bdb3

Browse files
adamlazik1adamruzicka
authored andcommitted
Fixes #38478 - Introduce SSH CA certificate support
1 parent 5e6cee8 commit f01bdb3

File tree

7 files changed

+85
-4
lines changed

7 files changed

+85
-4
lines changed

app/lib/proxy_api/remote_execution_ssh.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ 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 RestClient::ResourceNotFound => e
17+
Rails.logger.warn(format(N_("Unable to fetch CA public key: %{error}"), error: e.message))
18+
nil
19+
rescue => e
20+
raise ProxyException.new(url, e, N_('Unable to fetch CA public key'))
21+
end
22+
1423
def drop_from_known_hosts(hostname)
1524
delete('known_hosts/' + hostname)
1625
rescue => e

app/models/concerns/foreman_remote_execution/host_extensions.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ 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+
# only include public keys from SSH proxies that don't have SSH cert verification configured
112+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey if proxy.ca_pubkey.blank? }.compact.uniq
113+
end
114+
115+
def remote_execution_ssh_ca_keys
116+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey }.compact.uniq
112117
end
113118

114119
def drop_execution_interface_cache
@@ -139,10 +144,13 @@ def infrastructure_host?
139144

140145
def extend_host_params_hash(params)
141146
keys = remote_execution_ssh_keys
147+
ca_keys = remote_execution_ssh_ca_keys
142148
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}
149+
{keys: keys, ca_keys: ca_keys}.each do |key_set_name, key_set|
150+
if key_set.present?
151+
value, safe_value = params.fetch("remote_execution_ssh_#{key_set_name}", {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
152+
params["remote_execution_ssh_#{key_set_name}"] = {:value => value + key_set, :safe_value => safe_value + key_set, :source => source}
153+
end
146154
end
147155
[:remote_execution_ssh_user, :remote_execution_effective_user_method,
148156
:remote_execution_connect_by_ip].each do |key|

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
@@ -213,6 +213,7 @@
213213
extend_template_helpers ForemanRemoteExecution::RendererMethods
214214

215215
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
216+
extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/ca_pubkey'
216217
extend_rabl_template 'api/v2/interfaces/main', 'api/v2/interfaces/execution_flag'
217218
extend_rabl_template 'api/v2/subnets/show', 'api/v2/subnets/remote_execution_proxies'
218219
extend_rabl_template 'api/v2/hosts/main', 'api/v2/host/main'

test/unit/concerns/host_extensions_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
1212

1313
before do
1414
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
15+
SmartProxy.any_instance.stubs(:ca_pubkey).returns(nil)
1516
Setting[:remote_execution_ssh_user] = 'root'
1617
Setting[:remote_execution_effective_user_method] = 'sudo'
1718
end
@@ -61,6 +62,48 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
6162
end
6263
end
6364

65+
describe 'has ssh CA key configured' do
66+
let(:host) { FactoryBot.create(:host, :with_execution) }
67+
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
68+
let(:ca_sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJE bar@example.com' }
69+
70+
before do
71+
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
72+
SmartProxy.any_instance.stubs(:ca_pubkey).returns(ca_sshkey)
73+
Setting[:remote_execution_ssh_user] = 'root'
74+
Setting[:remote_execution_effective_user_method] = 'sudo'
75+
end
76+
77+
it 'has CA ssh keys in the parameters' do
78+
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
79+
end
80+
81+
it 'excludes ssh keys from proxies that have SSH CA key configured' do
82+
assert_empty host.remote_execution_ssh_keys
83+
end
84+
85+
it 'merges ssh CA keys from host parameters and proxies' do
86+
key = 'ssh-rsa not-even-a-key something@somewhere.com'
87+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => [key])
88+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
89+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
90+
end
91+
92+
it 'has ssh CA keys in the parameters even when no user specified' do
93+
FactoryBot.create(:smart_proxy, :ssh)
94+
host.interfaces.first.subnet.remote_execution_proxies.clear
95+
User.current = nil
96+
assert_includes host.remote_execution_ssh_ca_keys, ca_sshkey
97+
end
98+
99+
it 'merges ssh CA key as a string from host parameters and proxies' do
100+
key = 'ssh-rsa not-even-a-key something@somewhere.com'
101+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_ca_keys', :value => key)
102+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
103+
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
104+
end
105+
end
106+
64107
context 'host has multiple nics' do
65108
let(:host) { FactoryBot.build(:host, :with_execution) }
66109

0 commit comments

Comments
 (0)