Using a script that requires user input with chef cookbook with vagrant - vagrant

I'm writing a custom Asterisk chef cookbook where I need to run this script
bash 'create asterisk keys' do
user 'root'
cwd File.dirname(source_path)
code <<-EOH
cd asterisk-#{node.version}*
./contrib/scripts/ast_tls_cert -C #{node.host} -O "#{node.box_name}" -d #{node.keys_dir}
EOH
action :nothing
end
This ast_tls_cert script will ask for several password inputs, but when I run this through vagrant the keys never get generated since the passwords never get entered. Is there a way to tell chef that if the script requires user input to just use some ENV variable as the value? I don't really need it to stop and ask the user for the inupt. Actually, I'd rather it didn't do that. I just want to specify some value and tell it to use that value.

In general you need to assume Chef is running unattended. You can use tools like expect or pexpect(python version) to drive scripts that absolutely require interactive input, but check if you can provide the passwords via environment variables or similar.

There's a gem called ruby_expect which can be added into a cookbook to handle this.
At the top of your cookbook default.rb file you'll want to add in chef_gem 'ruby_expect'. Next I created a ruby_block to handle doing this.
ruby_block 'create asterisk keys' do
block do
require 'ruby_expect'
Dir.chdir(File.join(File.dirname(tarball_path), "asterisk-#{node.version}"))
exp = RubyExpect::Expect.spawn(%{./contrib/scripts/ast_tls_cert -C #{node.host} -O "#{node.box_name}" -d #{node.keys_dir}}, debug: true)
exp.procedure do
each do
expect %r{Enter pass phrase for /etc/asterisk/keys/ca.key:} do
send 'somepassword'
end
end
end
end
action :nothing
end
Where tarball_path is where you downloaded the asterisk tar.

Related

Strange behavior with ruby_block resource in Chef

I have two ruby blocks at the end of a recipe:
ruby_block 'set permissions for app dir' do
block do
require 'fileutils'
FileUtils.chown_R 'user01', 'user01', '/mnt/app/'
end
action :run
end
ruby_block 'configure node app session' do
block do
cmd = "sudo su - user01 -c \"/mnt/app/http-app-/bin/app create /mnt/app/http-app/#{node['hostname']}\" && sudo su -c 'systemctl enable app' && sudo su -c 'systemctl start app'"
exec(cmd)
end
action :run
not_if "stat -c %U /mnt/app/#{node['hostname']} |grep app"
end
A couple strange things are happening. One, I cannot add any code after the last block... it will not run if added. Two, when the cookbook runs the recipe never ends with if the run failed or was successful. Bootstrapping the system a second time will prove to finish successful... but ssh'ing to the box and running chef-client comes back with an empty run list.
Can anyone explain this behavior? How can i fix it?
exec() is not what you think. That's a Ruby core method which calls the actual exec() syscall, which replaces the current process with something new. What you want is our shell_out!() helper which runs a subcommand and returns and object with the results.

Can I use guards to chef if a windows service is running?

I'm writing a chef recipe and on this I need to perform an operation (run a batch) only if a service is not working.
I use this snippet:
batch 'run commnad' do
cwd target_path + '/bin/win64'
code 'command to be executed'
not_if '::Win32::Service.exists?("Service name")'
end
But it does not seems to work. After seeing this question I changed the process using an if clause instead of the guard and it works fine:
if !::Win32::Service.exists?("Service name") then
batch 'Install zabbix agent' do
cwd target_path + '/bin/win64'
code 'command to be executed'
end
end
But this should not be, for what I understood, the right way to manage this, so I'm wondering: why is the guard not working properly?
Thanks,
Michele.
The way you wrote your not_if statement runs the command as a shell script.
The shell doesn't know Ruby code, so the whole command will fail.
Need to first:
require win32/service
In order to use not_if with Ruby code you should put it inside a block instead:
not_if { ::Win32::Service.exists?("Service name") }
See some more examples here (search for not_if on the page):
https://docs.chef.io/resource_common.html
Here is the working example (Chef 13)
require 'win32/service'
windows_service "jenkins" do
action [:stop, :disable]
only_if { ::Win32::Service.exists?("jenkins")}
end

Chef run sh script

I have a problem trying to run shell script via Chef (with docker-provisioning).
This is how I try to execute my script:
bash 'shell_try' do
user "root"
run = "#{some_path_to_script}/my_script.sh some_params"
code " #{run} > stdout.txt 2> stderr.txt"
end
(note that this script should run another scripts, processes and write logs)
Here's no errors in the output, but when I log into machine and run ps aux process isn't running.
I guess something wrong with permissions (or env variables), because when I try the same command manually - it works.
A bash resource just runs the provided script text directly, if you wanted to run a long-running process generally you would set up an Upstart or systemd service and use the service resource to start it.
Finally find a solution (thanks to #coderanger) -
Install supervisor:
Download supervisor cookbook
Add:
include_recipe 'supervisor::default'
Add my service to supervisor:
supervisor_service "name" do
action :enable
#action :start
command '/path/script.sh start'
end
Run supervisor service
All done!
Please see the Chef documentation for your resource: https://docs.chef.io/resource_bash.html. The bash resource does not support a run attribute. Text of the code attribute is run as a bash script. The default action is to run the script unless told otherwise by the resource.
bash 'shell_try' do
user "root"
code " #{run} > stdout.txt 2> stderr.txt"
action :run
end
The code attribute is written to a temporary file where it is then run using the attributes specified in the resource.
The line run = "#{some_path_to_script}/my_script.sh some_params" at this point does nothing.

How do you prompt for a sudo password using Ruby?

Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user. I am aware of one similar question on SO where the answer was to run the same script twice and execute it as sudo, however that is not sufficient for me. Some times I need to revert to being a normal user after a sudo operation.
I have written the following in Ruby to do this
#!/usr/bin/ruby
require 'rubygems'
require 'highline/import'
require 'pty'
require 'expect'
def sudorun(command, password)
`sudo -k`
PTY.spawn("sleep 1; sudo -u root #{command} 2>&1") { | stdin, stdout, pid |
begin
stdin.expect(/password/) {
stdout.write("#{password}\n")
puts stdin.read.lstrip
}
rescue Errno::EIO
end
}
end
Unfortunately, using that code if the user enters the wrong password the script crashes. Ideally it should give the user 3 tries to get the sudo password right. How do I fix this?
I am running this on Linux Ubuntu BTW.
In my opinion, running a script that does stuff internally with sudo is wrong. A better approach is to have the user run the whole script with sudo, and have the script fork lesser-privileged children to do stuff:
# Drops privileges to that of the specified user
def drop_priv user
Process.initgroups(user.username, user.gid)
Process::Sys.setegid(user.gid)
Process::Sys.setgid(user.gid)
Process::Sys.setuid(user.uid)
end
# Execute the provided block in a child process as the specified user
# The parent blocks until the child finishes.
def do_as_user user
unless pid = fork
drop_priv(user)
yield if block_given?
exit! 0 # prevent remainder of script from running in the child process
end
puts "Child running as PID #{pid} with reduced privs"
Process.wait(pid)
end
at_exit { puts 'Script finished.' }
User = Struct.new(:username, :uid, :gid)
user = User.new('nobody', 65534, 65534)
do_as_user(user) do
sleep 1 # do something more useful here
exit! 2 # optionally provide an exit code
end
puts "Child exited with status #{$?.exitstatus}"
puts 'Running stuff as root'
sleep 1
do_as_user(user) do
puts 'Doing stuff as a user'
sleep 1
end
This example script has two helper methods. #drop_priv takes an object with username, uid, and gid defined and properly reduces the permissions of the executing process. The #do_as_user method calls #drop_priv in a child process before yielding to the provided block. Note the use of #exit! to prevent the child from running any part of the script outside of the block while avoiding the at_exit hook.
Often overlooked security concerns to think about:
Inheritance of open file descriptors
Environment variable filtering
Run children in a chroot?
Depending on what the script is doing, any of these may need to be addressed. #drop_priv is an ideal place to handle all of them.
If it is possible, you could move the stuff you want executed as root to a seperate file and use the system() function to run it as sudo, including the sudo prompt etc:
system("sudo ruby stufftorunasroot.rb")
The system() function is blocking, so the flow of your program doesn't need to be changed.
I do not know if this is what you want or need, but have you tried sudo -A (search the web or the man page for SUDO_ASKPASS which might have a value like /usr/lib/openssh/gnome-ssh-askpass or similar)? This is what I use when I need to present a graphical password dialogue to users in GUI environments.
Sorry if this is the wrong answer, maybe you really want to remain on the console.
#!/usr/bin/ruby
# ... blabla, other code
# part which requires sudo:
system "sudo -p 'sudo password: ' #{command}"
# other stuff
# sudo again
system "sudo -p 'sudo password: ' #{command}"
# usually sudo 'remembers' that you just authenticated yourself successfuly and doesn't ask for the PW again...
# some more code...

Chef shell script not being run

I am using Chef on Scalarium to download an agent and run various commands on it. What I'm attempting is to write a shell script in the recipe to perform this.
file "/etc/profile.d/blah.sh" do
content <<-EOH
sudo -sH
<Retrieve file and run some commands>
EOH
end
When I run the recipe in Scalarium, no errors occur, but the commands aren't run either. There's no errors in the commands themselves, as I've run them on my computer.
The recipe is definitely read, as the Chef logs contain Processing file[/etc/profile.d/blah.sh] on blah.localdomain.
I've never used Chef before, do I need to do something else to tell it to execute the shell script?
Perhaps you want something like:
file "/etc/profile.d/blah.sh" do
mode 0500
content <<-EOH
sudo -sH
<Retrieve file and run some commands>
EOH
end
execute "/etc/profile.d/blah.sh"
Or, you can put the file retrieval and running of commands directly into your chef recipe:
remote_file "/path/to/where/the/file/should/be/saved" do
source "https://example.com/path/to/where/the/file/comes/from"
end
execute "first command"
execute "second command"

Resources