Skip to content

Commit ae0434b

Browse files
authored
Fixes #39199 - Prevent spurious calls to the proxy related to pubkey/ca pubkey refresh (#1026)
* Fixes #39199 - Prevent spurious calls to proxy when rendering api responses The previous "return stored value or fetch a fresh one if missing" approach used for pubkey and ca_pubkey could cause calls to the proxy in unexpected situations. This could lead to problems like not being able to list smart proxies if the proxy which didn't have one of the pubkey fields filled was down. This change more strictly separates when values are only read and when values can be updated as part of reading. * Refs #39199 - Prevent spurious proxy calls when accessing host parameters
1 parent 0588a71 commit ae0434b

6 files changed

Lines changed: 119 additions & 12 deletions

File tree

app/models/concerns/foreman_remote_execution/host_extensions.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ def remote_execution_proxies(provider, authorized = true)
109109

110110
def remote_execution_ssh_keys
111111
# 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
112+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.pubkey(refresh: false) if proxy.ca_pubkey(refresh: false).blank? }.compact.uniq
113113
end
114114

115115
def remote_execution_ssh_ca_keys
116-
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey }.compact.uniq
116+
remote_execution_proxies(%w(SSH Script), false).values.flatten.uniq.map { |proxy| proxy.ca_pubkey(refresh: false) }.compact.uniq
117117
end
118118

119119
def drop_execution_interface_cache

app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ def self.prepended(base)
77
end
88
end
99

10-
def pubkey
11-
self[:pubkey] || update_pubkey
10+
def pubkey(refresh: true)
11+
self[:pubkey] || (refresh && update_pubkey || nil)
1212
end
1313

14-
def ca_pubkey
15-
self[:ca_pubkey] || update_ca_pubkey
14+
def ca_pubkey(refresh: true)
15+
self[:ca_pubkey] || (refresh && update_ca_pubkey || nil)
1616
end
1717

1818
def update_pubkey
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
attribute :ca_pubkey => :remote_execution_ca_pubkey
1+
node(:remote_execution_ca_pubkey) { |p| p.ca_pubkey(refresh: false) }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
attribute :pubkey => :remote_execution_pubkey
1+
node(:remote_execution_pubkey) { |p| p.pubkey(refresh: false) }

test/unit/concerns/host_extensions_test.rb

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
1111
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
1212

1313
before do
14-
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
15-
SmartProxy.any_instance.stubs(:ca_pubkey).returns(nil)
14+
host.subnet.remote_execution_proxies.each do |proxy|
15+
proxy.update(pubkey: sshkey, ca_pubkey: nil)
16+
end
1617
Setting[:remote_execution_ssh_user] = 'root'
1718
Setting[:remote_execution_effective_user_method] = 'sudo'
1819
end
@@ -60,6 +61,17 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
6061
User.current = nil
6162
assert_includes host.remote_execution_ssh_keys, sshkey
6263
end
64+
65+
it 'triggers no calls to the proxy' do
66+
host.subnet.remote_execution_proxies.each do |proxy|
67+
proxy.update(pubkey: nil)
68+
end
69+
70+
SmartProxy.any_instance.expects(:update_pubkey).never
71+
SmartProxy.any_instance.expects(:update_ca_pubkey).never
72+
73+
host.host_param('remote_execution_ssh_keys')
74+
end
6375
end
6476

6577
describe 'has ssh CA key configured' do
@@ -68,8 +80,9 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
6880
let(:ca_sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJE bar@example.com' }
6981

7082
before do
71-
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
72-
SmartProxy.any_instance.stubs(:ca_pubkey).returns(ca_sshkey)
83+
host.subnet.remote_execution_proxies.each do |proxy|
84+
proxy.update(pubkey: sshkey, ca_pubkey: ca_sshkey)
85+
end
7386
Setting[:remote_execution_ssh_user] = 'root'
7487
Setting[:remote_execution_effective_user_method] = 'sudo'
7588
end
@@ -102,6 +115,17 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
102115
assert_includes host.host_param('remote_execution_ssh_ca_keys'), key
103116
assert_includes host.host_param('remote_execution_ssh_ca_keys'), ca_sshkey
104117
end
118+
119+
it 'triggers no calls to the proxy' do
120+
host.subnet.remote_execution_proxies.each do |proxy|
121+
proxy.update(ca_pubkey: nil)
122+
end
123+
124+
SmartProxy.any_instance.expects(:update_pubkey).never
125+
SmartProxy.any_instance.expects(:update_ca_pubkey).never
126+
127+
host.host_param('remote_execution_ssh_ca_keys')
128+
end
105129
end
106130

107131
context 'host has multiple nics' do
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'test_plugin_helper'
2+
3+
class ForemanRemoteExecutionSmartProxyExtensionsTest < ActiveSupport::TestCase
4+
let(:proxy) { FactoryBot.create(:smart_proxy, :ssh) }
5+
6+
describe '#pubkey' do
7+
context 'when key is cached in the database' do
8+
it 'returns the cached key without fetching from proxy' do
9+
proxy.expects(:update_pubkey).never
10+
assert_equal 'ssh-rsa AAAAB3N...', proxy.pubkey
11+
assert_equal 'ssh-rsa AAAAB3N...', proxy.pubkey(refresh: false)
12+
end
13+
end
14+
15+
context 'when key is not cached' do
16+
before { proxy.update(pubkey: nil) }
17+
18+
it 'fetches from proxy by default' do
19+
proxy.expects(:update_pubkey).returns('ssh-rsa FETCHED...')
20+
assert_equal 'ssh-rsa FETCHED...', proxy.pubkey
21+
end
22+
23+
it 'returns nil without fetching when refresh: false' do
24+
proxy.expects(:update_pubkey).never
25+
assert_nil proxy.pubkey(refresh: false)
26+
end
27+
end
28+
end
29+
30+
describe '#ca_pubkey' do
31+
context 'when key is cached in the database' do
32+
before { proxy.update(ca_pubkey: 'ssh-rsa CA_KEY...') }
33+
34+
it 'returns the cached key without fetching from proxy' do
35+
proxy.expects(:update_ca_pubkey).never
36+
assert_equal 'ssh-rsa CA_KEY...', proxy.ca_pubkey
37+
assert_equal 'ssh-rsa CA_KEY...', proxy.ca_pubkey(refresh: false)
38+
end
39+
end
40+
41+
context 'when key is not cached' do
42+
before { proxy.update(ca_pubkey: nil) }
43+
44+
it 'fetches from proxy by default' do
45+
proxy.expects(:update_ca_pubkey).returns('ssh-rsa FETCHED_CA...')
46+
assert_equal 'ssh-rsa FETCHED_CA...', proxy.ca_pubkey
47+
end
48+
49+
it 'returns nil without fetching when refresh: false' do
50+
proxy.expects(:update_ca_pubkey).never
51+
assert_nil proxy.ca_pubkey(refresh: false)
52+
end
53+
end
54+
end
55+
56+
describe '#refresh' do
57+
context 'when the key is cached' do
58+
before do
59+
proxy.update(pubkey: 'ssh-rsa KEY...', ca_pubkey: 'ssh-rsa CA_KEY...')
60+
end
61+
62+
it 'fetches the keys' do
63+
proxy.expects(:update_pubkey).once
64+
proxy.expects(:update_ca_pubkey).once
65+
66+
proxy.refresh
67+
end
68+
end
69+
70+
context 'when the key is not cached' do
71+
before do
72+
proxy.update(pubkey: nil, ca_pubkey: nil)
73+
end
74+
75+
it 'fetches the keys' do
76+
proxy.expects(:update_pubkey).once
77+
proxy.expects(:update_ca_pubkey).once
78+
79+
proxy.refresh
80+
end
81+
end
82+
end
83+
end

0 commit comments

Comments
 (0)