Quote IdentityFile paths with spaces in ANSIBLE_SSH_ARGS

Fixes an issue where ANSIBLE_SSH_ARGS IdentityFile paths containing
spaces were not quoted, causing Ansible/OpenSSH to misinterpret them as
hostnames or options. Quote paths with spaces and continue to escape '%'
characters.

Fixes #9597.

Powered-by: GitHub Copilot in VS Code <https://code.visualstudio.com/docs/copilot/overview>
Powered-by: GitHub Codespaces <https://github.com/features/codespaces>
This commit is contained in:
GPT-5 mini 2025-10-05 13:07:00 +00:00 committed by 林博仁 Buo-ren Lin
parent f2960d5458
commit 5dd4a72a2d
2 changed files with 29 additions and 1 deletions

View file

@ -302,7 +302,18 @@ module VagrantPlugins
# Multiple Private Keys
unless !config.inventory_path && @ssh_info[:private_key_path].size == 1
@ssh_info[:private_key_path].each do |key|
ssh_options += ["-o", "IdentityFile=%s" % [key.gsub('%', '%%')]]
# Escape any '%' characters to avoid formatting issues when
# building the final command string for Ansible. If the path
# contains spaces, wrap it in double quotes so the OpenSSH
# client invoked by Ansible treats the whole path as a single
# argument (otherwise the path would be split on spaces and
# could be interpreted as a hostname or separate option).
escaped = key.gsub('%', '%%')
if escaped.include?(' ')
ssh_options += ["-o", "IdentityFile=\"%s\"" % [escaped]]
else
ssh_options += ["-o", "IdentityFile=%s" % [escaped]]
end
end
end

View file

@ -856,6 +856,23 @@ VF
end
end
describe "with an identity file path containing spaces and a custom inventory_path" do
before do
ssh_info[:private_key_path] = ['/path/with a space/key']
# When inventory_path is provided, the provisioner will add IdentityFile
# entries to ANSIBLE_SSH_ARGS even if there's a single key. Use a value
# containing spaces to reproduce the problematic scenario.
config.inventory_path = '/some inventory/with spaces/inv'
end
it "wraps the IdentityFile path in double quotes inside ANSIBLE_SSH_ARGS" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=\"/path/with a space/key\"")
}.and_return(default_execute_result)
end
end
describe "with an identity file containing `%`" do
before do
ssh_info[:private_key_path] = ['/foo%bar/key', '/bar%%buz/key']