Commit 609bea29 authored by Ash McKenzie's avatar Ash McKenzie

Use .netrc over credentials in URL

parent 818177da
...@@ -3,14 +3,22 @@ ...@@ -3,14 +3,22 @@
require 'cgi' require 'cgi'
require 'uri' require 'uri'
require 'open3' require 'open3'
require 'fileutils'
require 'tmpdir'
module QA module QA
module Git module Git
class Repository class Repository
include Scenario::Actable include Scenario::Actable
attr_writer :password
attr_accessor :env_vars
def initialize def initialize
@ssh_cmd = "" # We set HOME to the current working directory (which is a
# temporary directory created in .perform()) so the temporarily dropped
# .netrc can be utilised
self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
end end
def self.perform(*args) def self.perform(*args)
...@@ -23,14 +31,9 @@ module QA ...@@ -23,14 +31,9 @@ module QA
@uri = URI(address) @uri = URI(address)
end end
def username=(name) def username=(username)
@username = name @username = username
uri.user = name @uri.user = username
end
def password=(pass)
@password = pass
uri.password = CGI.escape(pass).gsub('+', '%20')
end end
def use_default_credentials def use_default_credentials
...@@ -41,10 +44,12 @@ module QA ...@@ -41,10 +44,12 @@ module QA
self.username = Runtime::User.username self.username = Runtime::User.username
self.password = Runtime::User.password self.password = Runtime::User.password
end end
add_credentials_to_netrc unless ssh_key_set?
end end
def clone(opts = '') def clone(opts = '')
run_and_redact_credentials(build_git_command("git clone #{opts} #{uri} ./")) run("git clone #{opts} #{uri} ./")
end end
def checkout(branch_name) def checkout(branch_name)
...@@ -60,12 +65,10 @@ module QA ...@@ -60,12 +65,10 @@ module QA
end end
def configure_identity(name, email) def configure_identity(name, email)
`git config user.name #{name}` run(%Q{git config user.name #{name}})
`git config user.email #{email}` run(%Q{git config user.email #{email}})
end
def configure_ssh_command(command) add_credentials_to_netrc
@ssh_cmd = "GIT_SSH_COMMAND='#{command}'"
end end
def commit_file(name, contents, message) def commit_file(name, contents, message)
...@@ -84,9 +87,7 @@ module QA ...@@ -84,9 +87,7 @@ module QA
end end
def push_changes(branch = 'master') def push_changes(branch = 'master')
output, _ = run_and_redact_credentials(build_git_command("git push #{uri} #{branch}")) run("git push #{uri} #{branch}")
output
end end
def commits def commits
...@@ -104,28 +105,69 @@ module QA ...@@ -104,28 +105,69 @@ module QA
keyscan_params << uri.host keyscan_params << uri.host
run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}") run_and_redact_credentials("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
configure_ssh_command("ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}") self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end end
def delete_ssh_key def delete_ssh_key
return unless private_key_file return unless ssh_key_set?
private_key_file.close(true) private_key_file.close(true)
known_hosts_file.close(true) known_hosts_file.close(true)
end end
def build_git_command(command_str) private
[ssh_cmd, command_str].compact.join(' ')
attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
def debug?
Runtime::Env.respond_to?(:verbose?) && Runtime::Env.verbose?
end end
private def ssh_key_set?
!private_key_file.nil?
end
def run(command_str)
command = [env_vars, command_str, '2>&1'].compact.join(' ')
warn "DEBUG: command=[#{command}]" if debug?
output, _ = Open3.capture2(command)
output = output.chomp.gsub(/\s+$/, '')
warn "DEBUG: output=[#{output}]" if debug?
output
end
def tmp_netrc_directory
@tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
end
def netrc_file_path
@netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
end
def netrc_content
"machine #{uri.host} login #{username} password #{password}"
end
def netrc_already_contains_content?
File.exist?(netrc_file_path) &&
File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
end
attr_reader :uri, :username, :password, :ssh_cmd, :known_hosts_file, :private_key_file def add_credentials_to_netrc
# Despite libcurl supporting a custom .netrc location through the
# CURLOPT_NETRC_FILE environment variable, git does not support it :(
# Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
#
# This will create a .netrc in the correct working directory, which is
# a temporary directory created in .perform()
#
return if netrc_already_contains_content?
# Since the remote URL contains the credentials, and git occasionally FileUtils.mkdir_p(tmp_netrc_directory)
# outputs the URL. Note that stderr is redirected to stdout. File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
def run_and_redact_credentials(command) File.chmod(0600, netrc_file_path)
Open3.capture2("#{command} 2>&1 | sed -E 's#://[^@]+@#://****@#g'")
end end
end end
end end
......
...@@ -8,10 +8,8 @@ describe QA::Git::Repository do ...@@ -8,10 +8,8 @@ describe QA::Git::Repository do
end end
describe '#clone' do describe '#clone' do
it 'redacts credentials from the URI in output' do it 'is unable to resolve host' do
output, _ = repository.clone expect(repository.clone).to include("fatal: unable to access 'http://root@foo/bar.git/'")
expect(output).to include("fatal: unable to access 'http://****@foo/bar.git/'")
end end
end end
...@@ -20,10 +18,8 @@ describe QA::Git::Repository do ...@@ -20,10 +18,8 @@ describe QA::Git::Repository do
`git init` # need a repo to push from `git init` # need a repo to push from
end end
it 'redacts credentials from the URI in output' do it 'fails to push changes' do
output, _ = repository.push_changes expect(repository.push_changes).to include("error: failed to push some refs to 'http://root@foo/bar.git'")
expect(output).to include("error: failed to push some refs to 'http://****@foo/bar.git'")
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment