summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-02-02 14:01:43 -0800
committerGitHub <noreply@github.com>2021-02-02 14:01:43 -0800
commitd118631c60569c049abe323e635463e7719758fe (patch)
tree128d4a1bdfb301e454c34f73b528bfc8b0b931fa
parent93a4bd6a3206637b32b5248bf82605efb303e2a8 (diff)
parent36dbbdc8494401e8859aec7e45b468d82454a013 (diff)
downloadchef-d118631c60569c049abe323e635463e7719758fe.tar.gz
Merge pull request #10971 from chef/su
handles su - USER session to perform bootstrap
-rw-r--r--lib/chef/knife/bootstrap.rb58
-rw-r--r--spec/unit/knife/bootstrap_spec.rb45
2 files changed, 96 insertions, 7 deletions
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 1550c62dc1..340ffaecfd 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -217,6 +217,16 @@ class Chef
description: "Execute the bootstrap via sudo with password.",
boolean: false
+ # runtime - su user
+ option :su_user,
+ long: "--su-user NAME",
+ description: "The su - USER name to perform bootstrap command using a non-root user."
+
+ # runtime - su user password
+ option :su_password,
+ long: "--su-password PASSWORD",
+ description: "The su USER password for authentication."
+
# runtime - client_builder
option :chef_node_name,
short: "-N NAME",
@@ -591,13 +601,31 @@ class Chef
def perform_bootstrap(remote_bootstrap_script_path)
ui.info("Bootstrapping #{ui.color(server_name, :bold)}")
cmd = bootstrap_command(remote_bootstrap_script_path)
- r = connection.run_command(cmd) do |data|
+ bootstrap_run_command(cmd)
+ end
+
+ # Actual bootstrap command to be run on the node.
+ # Handles recursive calls if su USER failed to authenticate.
+ def bootstrap_run_command(cmd)
+ r = connection.run_command(cmd) do |data, channel|
ui.msg("#{ui.color(" [#{connection.hostname}]", :cyan)} #{data}")
+ channel.send_data("#{config[:su_password] || config[:connection_password]}\n") if data.match?("Password:")
end
+
if r.exit_status != 0
ui.error("The following error occurred on #{server_name}:")
- ui.error(r.stderr)
- exit 1
+ ui.error("#{r.stdout} #{r.stderr}".strip)
+ exit(r.exit_status)
+ end
+ rescue Train::UserError => e
+ limit ||= 0
+ if e.reason == :bad_su_user_password && limit < 3
+ limit += 1
+ ui.warn("Failed to authenticate su - #{config[:su_user]} to #{server_name}")
+ config[:su_password] = ui.ask("Enter password for su - #{config[:su_user]}@#{server_name}:", echo: false)
+ retry
+ else
+ raise
end
end
@@ -1082,7 +1110,17 @@ class Chef
if connection.windows?
"cmd.exe /C #{remote_path}"
else
- "sh #{remote_path}"
+ cmd = "sh #{remote_path}"
+
+ if config[:su_user]
+ # su - USER is subject to required an interactive console
+ # Otherwise, it will raise: su: must be run from a terminal
+ set_transport_options(pty: true)
+ cmd = "su - #{config[:su_user]} -c '#{cmd}'"
+ cmd = "sudo " << cmd if config[:use_sudo]
+ end
+
+ cmd
end
end
@@ -1137,6 +1175,18 @@ class Chef
timeout.to_i
end
+
+ # Train::Transports::SSH::Connection#transport_options
+ # Append the options to connection transport_options
+ #
+ # @param opts [Hash] the opts to be added to connection transport_options.
+ # @return [Hash] transport_options if the opts contains any option to be set.
+ #
+ def set_transport_options(opts)
+ return unless opts.is_a?(Hash) || !opts.empty?
+
+ connection&.connection&.transport_options&.merge! opts
+ end
end
end
end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index 3797207b6d..294942c229 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -1726,7 +1726,8 @@ describe Chef::Knife::Bootstrap do
describe "#perform_bootstrap" do
let(:exit_status) { 0 }
- let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message") }
+ let(:stdout) { "" }
+ let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message", stdout: stdout) }
before do
allow(connection).to receive(:hostname).and_return "testhost"
@@ -1739,12 +1740,13 @@ describe Chef::Knife::Bootstrap do
expect(connection)
.to receive(:run_command)
.with("sh /path.sh")
- .and_yield("output here")
+ .and_yield("output here", nil)
.and_return result_mock
expect(knife.ui).to receive(:msg).with(/testhost/)
knife.perform_bootstrap("/path.sh")
end
+
context "when the remote command fails" do
let(:exit_status) { 1 }
it "shows an error and exits" do
@@ -1756,6 +1758,25 @@ describe Chef::Knife::Bootstrap do
expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit)
end
end
+
+ context "when the remote command failed due to su auth error" do
+ let(:exit_status) { 1 }
+ let(:stdout) { "su: Authentication failure" }
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ it "shows an error and exits" do
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("su - USER -c 'sh /path.sh'")
+ expect(connection)
+ .to receive(:run_command)
+ .with("su - USER -c 'sh /path.sh'")
+ .and_yield("output here", nil)
+ .and_raise(Train::UserError)
+ expect { knife.perform_bootstrap("/path.sh") }.to raise_error(Train::UserError)
+ end
+ end
end
describe "#connect!" do
@@ -1964,7 +1985,25 @@ describe Chef::Knife::Bootstrap do
context "under Linux" do
let(:linux_test) { true }
it "prefixes the command to run under sh" do
- expect(knife.bootstrap_command("bootstrap")).to eq "sh bootstrap"
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sh bootstrap.sh"
+ end
+
+ context "with --su-user option" do
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ before do
+ knife.config[:su_user] = "root"
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ end
+ it "prefixes the command to run using su -USER -c" do
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
+
+ it "sudo appended if --sudo option enabled" do
+ knife.config[:use_sudo] = true
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sudo su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
end
end
end