Skip to content

Commit 1702658

Browse files
committed
Refs #38478 - Introduce SSH CA certificate support
1 parent c0b9d25 commit 1702658

File tree

6 files changed

+53
-2
lines changed

6 files changed

+53
-2
lines changed

lib/smart_proxy_remote_execution_ssh.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ def public_key_file
2121
File.expand_path("#{private_key_file}.pub")
2222
end
2323

24+
def cert_file
25+
File.expand_path("#{private_key_file}-cert.pub")
26+
end
27+
28+
def ca_public_key_file
29+
path = Plugin.settings.ssh_user_ca_public_key_file
30+
File.expand_path(path) if present?(path)
31+
end
32+
2433
def validate_mode!
2534
Plugin.settings.mode = Plugin.settings.mode.to_sym
2635

@@ -64,14 +73,23 @@ def validate_ssh_settings!
6473
end
6574

6675
unless File.exist?(private_key_file)
67-
raise "SSH public key file #{private_key_file} doesn't exist.\n"\
76+
raise "SSH private key file #{private_key_file} doesn't exist.\n"\
6877
"You can generate one with `ssh-keygen -t rsa -b 4096 -f #{private_key_file} -N ''`"
6978
end
7079

7180
unless File.exist?(public_key_file)
7281
raise "SSH public key file #{public_key_file} doesn't exist"
7382
end
7483

84+
if present?(Plugin.settings.ssh_user_ca_public_key_file)
85+
{ ca_public_key_file: 'CA public key', cert_file: 'certificate' }.each do |file, label|
86+
file_path = send(file)
87+
unless file_path && File.exist?(file_path)
88+
raise "SSH #{label} file '#{file_path}' doesn't exist"
89+
end
90+
end
91+
end
92+
7593
validate_ssh_log_level!
7694
end
7795

@@ -114,6 +132,12 @@ def job_storage
114132
def with_mqtt?
115133
Proxy::RemoteExecution::Ssh::Plugin.settings.mode == :'pull-mqtt'
116134
end
135+
136+
private
137+
138+
def present?(value)
139+
value && !value.empty?
140+
end
117141
end
118142
end
119143
end

lib/smart_proxy_remote_execution_ssh/api.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ class Api < ::Sinatra::Base
1313
File.read(Ssh.public_key_file)
1414
end
1515

16+
get "/ca_pubkey" do
17+
if Ssh.ca_public_key_file
18+
File.read(Ssh.ca_public_key_file)
19+
end
20+
end
21+
1622
if Proxy::RemoteExecution::Ssh::Plugin.settings.cockpit_integration
1723
post "/session" do
1824
do_authorize_any

lib/smart_proxy_remote_execution_ssh/multiplexed_ssh_connection.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ def initialize(options, logger:)
6060
@host_public_key = options.fetch(:host_public_key, nil)
6161
@verify_host = options.fetch(:verify_host, nil)
6262
@client_private_key_file = settings.ssh_identity_key_file
63+
@client_ca_known_hosts_file = settings.ssh_ca_known_hosts_file
64+
@client_ca_public_key_file = settings.ssh_user_ca_public_key_file
65+
@client_ca_cert_file = settings.ssh_ca_cert_file
66+
@client_cert_file = Proxy::RemoteExecution::Ssh.cert_file
6367

6468
@local_working_dir = options.fetch(:local_working_dir, settings.local_working_dir)
6569
@socket_working_dir = options.fetch(:socket_working_dir, settings.socket_working_dir)
@@ -154,9 +158,14 @@ def establish_ssh_options
154158
ssh_options << "-o User=#{@ssh_user}"
155159
ssh_options << "-o Port=#{@ssh_port}" if @ssh_port
156160
ssh_options << "-o IdentityFile=#{@client_private_key_file}" if @client_private_key_file
161+
ssh_options << "-o CertificateFile=#{@client_cert_file}" if @client_cert_file
157162
ssh_options << "-o IdentitiesOnly=yes"
158163
ssh_options << "-o StrictHostKeyChecking=accept-new"
159-
ssh_options << "-o UserKnownHostsFile=#{prepare_known_hosts}" if @host_public_key
164+
if @host_public_key
165+
ssh_options << "-o UserKnownHostsFile=#{prepare_known_hosts}"
166+
elsif @client_ca_known_hosts_file
167+
ssh_options << "-o UserKnownHostsFile=#{@client_ca_known_hosts_file}"
168+
end
160169
ssh_options << "-o LogLevel=#{ssh_log_level(true)}"
161170
ssh_options << "-o ControlMaster=auto"
162171
ssh_options << "-o ControlPath=#{socket_file}"

lib/smart_proxy_remote_execution_ssh/plugin.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class Plugin < Proxy::Plugin
1212

1313
settings_file "remote_execution_ssh.yml"
1414
default_settings :ssh_identity_key_file => '~/.ssh/id_rsa_foreman_proxy',
15+
# :ssh_ca_known_hosts_file => nil,
16+
# :ssh_user_ca_public_key_file => nil,
1517
:ssh_user => 'root',
1618
:remote_working_dir => '/var/tmp',
1719
:local_working_dir => '/var/tmp',

test/api_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ def setup
4141
end
4242
end
4343

44+
describe '/ca_pubkey' do
45+
it 'returns the content of the CA public key' do
46+
get '/ca_pubkey'
47+
_(last_response.body).must_equal '===ca-public-key==='
48+
end
49+
end
50+
4451
describe 'job storage' do
4552
let(:uuid) { SecureRandom.uuid }
4653
let(:execution_plan_uuid) { SecureRandom.uuid }

test/test_helper.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DATA_DIR = File.expand_path('../data', __FILE__)
1717
FAKE_PRIVATE_KEY_FILE = File.join(DATA_DIR, 'fake_id_rsa')
1818
FAKE_PUBLIC_KEY_FILE = "#{FAKE_PRIVATE_KEY_FILE}.pub"
19+
FAKE_CA_PUBLIC_KEY_FILE = File.join(DATA_DIR, 'fake_ca_cert.pub')
1920

2021
logdir = File.join(File.dirname(__FILE__), '..', 'logs')
2122
FileUtils.mkdir_p(logdir) unless File.exist?(logdir)
@@ -24,9 +25,11 @@ def prepare_fake_keys
2425
Proxy::RemoteExecution::Ssh::Plugin.settings.ssh_identity_key_file = FAKE_PRIVATE_KEY_FILE
2526
# Workaround for Proxy::RemoteExecution::Ssh::Plugin.settings.ssh_identity_key_file returning nil
2627
Proxy::RemoteExecution::Ssh::Plugin.settings.stubs(:ssh_identity_key_file).returns(FAKE_PRIVATE_KEY_FILE)
28+
Proxy::RemoteExecution::Ssh::Plugin.settings.stubs(:ssh_user_ca_public_key_file).returns(FAKE_CA_PUBLIC_KEY_FILE)
2729
FileUtils.mkdir_p(DATA_DIR) unless File.exist?(DATA_DIR)
2830
File.write(FAKE_PRIVATE_KEY_FILE, '===private-key===')
2931
File.write(FAKE_PUBLIC_KEY_FILE, '===public-key===')
32+
File.write(FAKE_CA_PUBLIC_KEY_FILE, '===ca-public-key===')
3033
end
3134

3235
class Minitest::Test

0 commit comments

Comments
 (0)