Skip to content

Commit 3d3b8ae

Browse files
authored
Merge pull request #23482 from Fryguy/add_ansible_runner_appliance_tests
Add appliance/container tests for ansible-runner
2 parents 4d547a9 + 707b594 commit 3d3b8ae

File tree

9 files changed

+423
-139
lines changed

9 files changed

+423
-139
lines changed

config/brakeman.ignore

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
{
22
"ignored_warnings": [
3+
{
4+
"warning_type": "Command Injection",
5+
"warning_code": 14,
6+
"fingerprint": "57a67fd8c12d36c64fa21e5e7a93eeb6eac1f6fd4e6a1666f98c503574c46360",
7+
"check_name": "Execute",
8+
"message": "Possible command injection",
9+
"file": "lib/ansible/runner.rb",
10+
"line": 451,
11+
"link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
12+
"code": "`#{(File.join(venv_bin_path, \"ansible\") or \"ansible\")} --version 2>/dev/null`",
13+
"render_path": null,
14+
"location": {
15+
"type": "method",
16+
"class": "Ansible::Runner",
17+
"method": "s(:self).ansible_version_raw"
18+
},
19+
"user_input": "File.join(venv_bin_path, \"ansible\")",
20+
"confidence": "Medium",
21+
"cwe_id": [
22+
77
23+
],
24+
"note": "This method is safe because it only uses hardcoded paths, which cannot be changed by user input."
25+
},
326
{
427
"warning_type": "Unmaintained Dependency",
528
"warning_code": 121,
@@ -22,18 +45,18 @@
2245
{
2346
"warning_type": "Command Injection",
2447
"warning_code": 14,
25-
"fingerprint": "9a58ac820e59b1edb4530e27646edc1f328915a7a356d987397659b48c52239e",
48+
"fingerprint": "c3234e1d86168cb0ac61761b99bdbbf493491fe9b1b5f808889c46e17a6aa781",
2649
"check_name": "Execute",
2750
"message": "Possible command injection",
2851
"file": "lib/ansible/runner.rb",
29-
"line": 430,
52+
"line": 445,
3053
"link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
3154
"code": "`python#{version} -c 'import site; print(\":\".join(site.getsitepackages()))'`",
3255
"render_path": null,
3356
"location": {
3457
"type": "method",
3558
"class": "Ansible::Runner",
36-
"method": "s(:self).ansible_python_paths_raw"
59+
"method": "s(:self).python_path_raw"
3760
},
3861
"user_input": "version",
3962
"confidence": "Medium",
@@ -43,6 +66,6 @@
4366
"note": "This method is safe because it verifies that the version is in the form #.#."
4467
}
4568
],
46-
"updated": "2025-03-31 10:21:49 -0400",
69+
"updated": "2025-06-16 22:28:03 -0400",
4770
"brakeman_version": "6.2.2"
4871
}

lib/ansible/content.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ def initialize(path)
88
@path = Pathname.new(path)
99
end
1010

11-
def fetch_galaxy_roles
11+
def fetch_galaxy_roles(env = {})
1212
return true unless requirements_file.exist?
1313

1414
require "awesome_spawn"
15-
AwesomeSpawn.run!("ansible-galaxy", :params => ["install", {:roles_path= => roles_dir, :role_file= => requirements_file}])
15+
AwesomeSpawn.run!("ansible-galaxy", :env => env, :params => ["install", {:roles_path= => roles_dir, :role_file= => requirements_file}])
1616
end
1717

1818
def self.fetch_plugin_galaxy_roles

lib/ansible/runner.rb

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ class << self
44
def available?
55
return @available if defined?(@available)
66

7-
@available = system("which ansible-runner >/dev/null 2>&1")
7+
@available = system(runner_env, "which ansible-runner >/dev/null 2>&1")
8+
end
9+
10+
def runner_env
11+
@runner_env ||= {
12+
"PYTHONPATH" => [venv_python_path, ansible_python_path].compact.join(File::PATH_SEPARATOR),
13+
"PATH" => [venv_bin_path, ENV["PATH"].presence].compact.join(File::PATH_SEPARATOR)
14+
}.delete_blanks
815
end
916

1017
# Runs a playbook via ansible-runner, see: https://ansible-runner.readthedocs.io/en/latest/standalone.html#running-playbooks
@@ -221,6 +228,8 @@ def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_run
221228

222229
params = runner_params(base_dir, ansible_runner_method, playbook_or_role_args, verbosity)
223230

231+
# puts "#{env_vars_hash.map { |k, v| "#{k}=#{v}" }.join(" ")} #{AwesomeSpawn.build_command_line("ansible-runner", params)}"
232+
224233
begin
225234
fetch_galaxy_roles(playbook_or_role_args)
226235

@@ -317,11 +326,7 @@ def fetch_galaxy_roles(playbook_or_role_args)
317326
return unless playbook_or_role_args[:playbook]
318327

319328
playbook_dir = File.dirname(playbook_or_role_args[:playbook])
320-
Ansible::Content.new(playbook_dir).fetch_galaxy_roles
321-
end
322-
323-
def runner_env
324-
{"PYTHONPATH" => python_path}.delete_nils
329+
Ansible::Content.new(playbook_dir).fetch_galaxy_roles(runner_env)
325330
end
326331

327332
def credentials_info(credentials, base_dir)
@@ -408,34 +413,42 @@ def wait_for_listener_start(listener)
408413
end
409414
end
410415

411-
def python_path
412-
@python_path ||= [manageiq_venv_path, *ansible_python_paths].compact.join(File::PATH_SEPARATOR)
416+
VENV_ROOT = "/var/lib/manageiq/venv".freeze
417+
418+
def venv_python_path
419+
return @venv_python_path if defined?(@venv_python_path)
420+
421+
@venv_python_path = Dir.glob(File.join(VENV_ROOT, "lib/python#{ansible_python_version}/site-packages")).first
413422
end
414423

415-
def manageiq_venv_path
416-
Dir.glob("/var/lib/manageiq/venv/lib/python*/site-packages").first
424+
def venv_bin_path
425+
return @venv_bin_path if defined?(@venv_bin_path)
426+
427+
@venv_bin_path = Dir.glob(File.join(VENV_ROOT, "bin")).first
428+
end
429+
430+
def ansible_python_path
431+
python_path_raw(ansible_python_version).presence
417432
end
418433

419-
def ansible_python_paths
420-
ansible_python_paths_raw(ansible_python_version).chomp.split(":")
434+
def ansible_python_version
435+
ansible_version_raw.match(/python version = (\d+\.\d+)\./)&.captures&.first
421436
end
422437

423438
# NOTE: This method is ignored by brakeman in the config/brakeman.ignore
424-
def ansible_python_paths_raw(version)
439+
def python_path_raw(version)
425440
return "" if version.blank?
426441

427442
# This check allows us to ignore the brakeman warning about command line injection
428-
raise "ansible python version is not a number: #{version}" unless version.match?(/^\d+\.\d+$/)
443+
raise "python version is not a number: #{version}" unless version.match?(/^\d+\.\d+$/)
429444

430445
`python#{version} -c 'import site; print(":".join(site.getsitepackages()))'`.chomp
431446
end
432447

433-
def ansible_python_version
434-
ansible_python_version_raw.match(/python version = (\d+\.\d+)\./)&.captures&.first
435-
end
436-
437-
def ansible_python_version_raw
438-
`ansible --version 2>/dev/null`.chomp
448+
# NOTE: This method is ignored by brakeman in the config/brakeman.ignore
449+
def ansible_version_raw
450+
ansible = venv_bin_path ? File.join(venv_bin_path, "ansible") : "ansible"
451+
`#{ansible} --version 2>/dev/null`.chomp
439452
end
440453
end
441454
end

lib/tasks/test_security_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def self.brakeman(format: "human")
2323
app_path = Rails.root.to_s
2424
engine_paths = Vmdb::Plugins.paths.except(ManageIQ::Schema::Engine).values
2525

26-
puts "** Running brakeman in #{app_path}"
26+
puts "** Running brakeman in #{app_path}#{" (interactive ignore)" if interactive_ignore}"
2727
puts "** engines:"
2828
puts "** - #{engine_paths.join("\n** - ")}"
2929

spec/lib/ansible/content_spec.rb

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
after { FileUtils.rm_rf(content_dir) }
99

1010
describe "#fetch_galaxy_roles" do
11+
let(:expected_params) do
12+
[
13+
"install",
14+
{
15+
:roles_path= => roles_dir,
16+
:role_file= => roles_requirements
17+
}
18+
]
19+
end
20+
1121
it "doesn't run anything if there is no requirements file" do
1222
expect(AwesomeSpawn).not_to receive(:run!)
1323

@@ -18,12 +28,7 @@
1828
FileUtils.mkdir(roles_dir)
1929
FileUtils.touch(roles_requirements)
2030

21-
expected_params = [
22-
"install",
23-
{:roles_path= => roles_dir,
24-
:role_file= => roles_requirements}
25-
]
26-
expect(AwesomeSpawn).to receive(:run!).with("ansible-galaxy", :params => expected_params)
31+
expect(AwesomeSpawn).to receive(:run!).with("ansible-galaxy", :env => {}, :params => expected_params)
2732

2833
subject.fetch_galaxy_roles
2934
end
@@ -32,14 +37,19 @@
3237
FileUtils.mkdir(roles_dir)
3338
FileUtils.touch(roles_requirements)
3439

35-
expected_params = [
36-
"install",
37-
{:roles_path= => roles_dir,
38-
:role_file= => roles_requirements}
39-
]
40-
expect(AwesomeSpawn).to receive(:run!).with("ansible-galaxy", :params => expected_params)
40+
expect(AwesomeSpawn).to receive(:run!).with("ansible-galaxy", :env => {}, :params => expected_params)
4141

4242
described_class.new(content_dir.to_s).fetch_galaxy_roles
4343
end
44+
45+
it "accepts an env" do
46+
FileUtils.mkdir(roles_dir)
47+
FileUtils.touch(roles_requirements)
48+
env = {"PYTHONPATH" => "/var/lib/manageiq/venv/python3.12/site-packages", "PATH" => "/var/lib/manageiq/venv/bin"}
49+
50+
expect(AwesomeSpawn).to receive(:run!).with("ansible-galaxy", :env => env, :params => expected_params)
51+
52+
subject.fetch_galaxy_roles(env)
53+
end
4454
end
4555
end

spec/lib/ansible/runner/data/aws.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- name: Test aws collection
2+
hosts: localhost
3+
tasks:
4+
- name: Connect to aws
5+
amazon.aws.ec2_instance_info:
6+
access_key: 'access_key'
7+
secret_key: 'secret_key'
8+
region: us-east-1
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
- name: Test vmware collection
2+
hosts: localhost
3+
tasks:
4+
- name: Connect to vcenter
5+
community.vmware.vmware_vm_info:
6+
hostname: 'vcenter_hostname'
7+
username: 'vcenter_username'
8+
password: 'vcenter_password'
9+
validate_certs: false

0 commit comments

Comments
 (0)