Skip to content
This repository has been archived by the owner on Nov 24, 2022. It is now read-only.

Commit

Permalink
Add support for using a sudo wrapper script
Browse files Browse the repository at this point in the history
Closes #90
  • Loading branch information
fgrehm committed Jul 28, 2013
1 parent 98f1df4 commit d62a053
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 90 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ BACKWARDS INCOMPATIBILITIES:
FEATURES:

- Add support for salt-minion and add latest dev release for ubuntu codenamed saucy [#116](https://github.com/fgrehm/vagrant-lxc/pull/116)
- Add support for using a sudo wrapper script [#90](https://github.com/fgrehm/vagrant-lxc/issues/90)

IMPROVEMENTS:

Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,41 @@ set from container's configuration file that is usually kept under
For other configuration options, please check [lxc.conf manpages](http://manpages.ubuntu.com/manpages/quantal/man5/lxc.conf.5.html).


### Avoiding `sudo` passwords

This plugin requires **a lot** of `sudo`ing since [user namespaces](https://wiki.ubuntu.com/LxcSecurity)
are not supported on mainstream kernels. In order to work around that we can use
a really dumb Ruby wrapper script like the one below and add a `NOPASSWD` entry
to our `/etc/sudoers` file:

```ruby
#!/usr/bin/env ruby
exec ARGV.join(' ')
```

For example, you can save the code above under your `/usr/bin/lxc-vagrant-wrapper`,
turn it into an executable script by running `chmod +x /usr/bin/lxc-vagrant-wrapper`
and add the line below to your `/etc/sudoers` file:

```
USERNAME ALL=NOPASSWD:/usr/bin/lxc-vagrant-wrapper
```

This comment has been minimized.

Copy link
@Caustic

Caustic Jul 31, 2013

Correct me if I'm wrong, wouldn't this allow USERNAME to run any privileged command without a password?

Wouldn't collecting all the programs that need sudo to get this plugin to work and making them NOPASSWD make more sense than a script that allows you to execute any command?

This comment has been minimized.

Copy link
@fgrehm

fgrehm Jul 31, 2013

Author Owner

@Caustic yes, that script is pretty dumb and insecure and that is why I'm not bundling it with the plugin. it is an optional configuration and is up to the user to enable it or not and to create the script the way they want.

for sure we could whitelist the allowed commands but to be really honest I don't have plans to do that as of now since I'm using it only on my personal laptop and don't have plans to do that on a real server any time soon. if someone is up for contributing a more intelligent script I'm up for bundling it with the plugin ;)

anyways, tks for pointing out, I'll add a big warning so that people is aware of the consequences :)


In order to tell vagrant-lxc to use that script when `sudo` is needed, you can
pass in the path to the script as a configuration for the provider:

```ruby
Vagrant.configure("2") do |config|
config.vm.provider :lxc do |lxc|
lxc.sudo_wrapper = '/usr/bin/lxc-vagrant-wrapper'
end
end
```

If you want to set the `sudo_wrapper` globally, just add the code above to your
`~/.vagrant.d/Vagrantfile`.


### Base boxes

Please check [the wiki](https://github.com/fgrehm/vagrant-lxc/wiki/Base-boxes)
Expand All @@ -108,8 +143,6 @@ base boxes and information on [how to build your own](https://github.com/fgrehm/

* The plugin does not detect forwarded ports collision, right now you are
responsible for taking care of that.
* There is a hell lot of `sudo`s involved and this will probably be around until
[user namespaces](https://wiki.ubuntu.com/LxcSecurity) are supported or I'm able to handle [#90](https://github.com/fgrehm/vagrant-lxc/issues/90)
* [Does not tell you if dependencies are not met](https://github.com/fgrehm/vagrant-lxc/issues/11)
(will probably just throw up some random error)
* + bunch of other [core features](https://github.com/fgrehm/vagrant-lxc/issues?labels=core&milestone=&page=1&state=open)
Expand Down
2 changes: 1 addition & 1 deletion lib/vagrant-lxc/action/remove_temporary_files.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def call(env)
if env[:machine].state.id == :stopped
@logger.debug 'Removing temporary files'
tmp_path = env[:machine].provider.driver.rootfs_path.join('tmp')
system "sudo rm -rf #{tmp_path}/*"
env[:machine].provider.sudo_wrapper.run('rm', '-rf', "#{tmp_path}/*")
end
end
end
Expand Down
26 changes: 25 additions & 1 deletion lib/vagrant-lxc/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ class Config < Vagrant.plugin("2", :config)
# @return [Array]
attr_reader :customizations

# A String that points to a file that acts as a wrapper for sudo commands.
#
# This allows us to have a single entry when whitelisting NOPASSWD commands
# on /etc/sudoers
attr_accessor :sudo_wrapper

def initialize
@customizations = []
@sudo_wrapper = UNSET_VALUE
end

# Customize the container by calling `lxc-start` with the given
Expand All @@ -25,7 +32,24 @@ def customize(key, value)
@customizations << [key, value]
end

# TODO: At some point in the future it would be nice to validate these options
def finalize!
@sudo_wrapper = nil if @sudo_wrapper == UNSET_VALUE
end

def validate(machine)
errors = []

if @sudo_wrapper
hostpath = Pathname.new(@sudo_wrapper).expand_path(machine.env.root_path)
if ! hostpath.file?
errors << I18n.t('vagrant_lxc.sudo_wrapper_not_found', path: hostpath.to_s)
elsif ! hostpath.executable?
errors << I18n.t('vagrant_lxc.sudo_wrapper_not_executable', path: hostpath.to_s)
end
end

{ "lxc provider" => errors }
end
end
end
end
16 changes: 9 additions & 7 deletions lib/vagrant-lxc/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ class ContainerNotFound < StandardError; end
attr_reader :container_name,
:customizations

def initialize(container_name, cli = CLI.new(container_name))
def initialize(container_name, sudo_wrapper, cli = nil)
@container_name = container_name
@cli = cli
@sudo_wrapper = sudo_wrapper
@cli = cli || CLI.new(sudo_wrapper, container_name)
@logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
@customizations = []
end
Expand Down Expand Up @@ -48,7 +49,7 @@ def share_folders(folders)
unless guestpath.directory?
begin
@logger.debug("Guest path doesn't exist, creating: #{guestpath}")
system "sudo mkdir -p #{guestpath.to_s}"
@sudo_wrapper.run('mkdir', '-p', guestpath.to_s)
rescue Errno::EACCES
raise Vagrant::Errors::SharedFolderCreateFailed, :path => guestpath.to_s
end
Expand Down Expand Up @@ -89,10 +90,11 @@ def compress_rootfs

Dir.chdir base_path do
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
system "sudo rm -f rootfs.tar.gz && sudo tar --numeric-owner -czf #{target_path} #{basename}/*"
@sudo_wrapper.run('rm', '-f', 'rootfs.tar.gz')
@sudo_wrapper.run('tar', '--numeric-owner', '-czf', target_path, "#{basename}/*")

@logger.info "Changing rootfs tarbal owner"
system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}"
@sudo_wrapper.run('chown', "#{ENV['USER']}:#{ENV['USER']}", target_path)
end

target_path
Expand Down Expand Up @@ -121,11 +123,11 @@ def import_template(path)
tmp_template_path = templates_path.join("lxc-#{template_name}").to_s

@logger.debug 'Copying LXC template into place'
system(%Q[sudo su root -c "cp #{path} #{tmp_template_path}"])
@sudo_wrapper.run('cp', path, tmp_template_path)

yield template_name
ensure
system(%Q[sudo su root -c "rm #{tmp_template_path}"])
@sudo_wrapper.run('rm', tmp_template_path)
end

TEMPLATES_PATH_LOOKUP = %w(
Expand Down
6 changes: 3 additions & 3 deletions lib/vagrant-lxc/driver/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ module Vagrant
module LXC
class Driver
class Builder
def self.build(id)
version = CLI.new.version.match(/^(\d+\.\d+)\./)[1].to_f
Driver.new(id).tap do |driver|
def self.build(id, shell)
version = CLI.new(shell).version.match(/^(\d+\.\d+)\./)[1].to_f
Driver.new(id, shell).tap do |driver|
mod = version >= 0.8 ?
Driver::FetchIpWithAttach :
Driver::FetchIpFromDsnmasq
Expand Down
63 changes: 6 additions & 57 deletions lib/vagrant-lxc/driver/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ def initialize(target_state, state)
end
end

# Include this so we can use `Subprocess` more easily.
include Vagrant::Util::Retryable

def initialize(name = nil)
@name = name
@logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
def initialize(sudo_wrapper, name = nil)
@sudo_wrapper = sudo_wrapper
@name = name
@logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
end

def list
Expand Down Expand Up @@ -66,7 +64,7 @@ def destroy
end

def start(overrides = [], extra_opts = [])
options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
options = overrides.map { |key, value| ["-s", "lxc.#{key}='#{value}'"] }.flatten
options += extra_opts if extra_opts
run :start, '-d', '--name', @name, *options
end
Expand Down Expand Up @@ -110,56 +108,7 @@ def transition_to(target_state, tries = 30, timeout = 1, &block)
private

def run(command, *args)
execute('sudo', "lxc-#{command}", *args)
end

# TODO: Review code below this line, it was pretty much a copy and
# paste from VirtualBox base driver and has no tests
def execute(*command, &block)
# Get the options hash if it exists
opts = {}
opts = command.pop if command.last.is_a?(Hash)

tries = 0
tries = 3 if opts[:retryable]

sleep = opts.fetch(:sleep, 1)

# Variable to store our execution result
r = nil

retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
# Execute the command
r = raw(*command, &block)

# If the command was a failure, then raise an exception that is
# nicely handled by Vagrant.
if r.exit_code != 0
if @interrupted
@logger.info("Exit code != 0, but interrupted. Ignoring.")
else
raise LXC::Errors::ExecuteError, :command => command.inspect
end
end
end

# Return the output, making sure to replace any Windows-style
# newlines with Unix-style.
r.stdout.gsub("\r\n", "\n")
end

def raw(*command, &block)
int_callback = lambda do
@interrupted = true
@logger.info("Interrupted.")
end

# Append in the options for subprocess
command << { :notify => [:stdout, :stderr] }

Vagrant::Util::Busy.busy(int_callback) do
Vagrant::Util::Subprocess.execute(*command, &block)
end
@sudo_wrapper.run("lxc-#{command}", *args)
end
end
end
Expand Down
11 changes: 10 additions & 1 deletion lib/vagrant-lxc/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "vagrant-lxc/action"
require "vagrant-lxc/driver"
require "vagrant-lxc/driver/builder"
require "vagrant-lxc/sudo_wrapper"

module Vagrant
module LXC
Expand All @@ -16,14 +17,22 @@ def initialize(machine)
machine_id_changed
end

def sudo_wrapper
@shell ||= begin
wrapper = @machine.provider_config.sudo_wrapper
wrapper = Pathname(wrapper).expand_path(@machine.env.root_path).to_s if wrapper
SudoWrapper.new(wrapper)
end
end

# If the machine ID changed, then we need to rebuild our underlying
# container.
def machine_id_changed
id = @machine.id

begin
@logger.debug("Instantiating the container for: #{id.inspect}")
@driver = Driver::Builder.build(id)
@driver = Driver::Builder.build(id, self.sudo_wrapper)
@driver.validate!
rescue Driver::ContainerNotFound
# The container doesn't exist, so we probably have a stale
Expand Down
69 changes: 69 additions & 0 deletions lib/vagrant-lxc/sudo_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module Vagrant
module LXC
class SudoWrapper
# Include this so we can use `Subprocess` more easily.
include Vagrant::Util::Retryable

def initialize(wrapper_path = nil)
@wrapper_path = wrapper_path
@logger = Log4r::Logger.new("vagrant::lxc::shell")
end

def run(*command)
command.unshift @wrapper_path if @wrapper_path
execute *(['sudo'] + command)
end

private

# TODO: Review code below this line, it was pretty much a copy and
# paste from VirtualBox base driver and has no tests
def execute(*command, &block)
# Get the options hash if it exists
opts = {}
opts = command.pop if command.last.is_a?(Hash)

tries = 0
tries = 3 if opts[:retryable]

sleep = opts.fetch(:sleep, 1)

# Variable to store our execution result
r = nil

retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
# Execute the command
r = raw(*command, &block)

# If the command was a failure, then raise an exception that is
# nicely handled by Vagrant.
if r.exit_code != 0
if @interrupted
@logger.info("Exit code != 0, but interrupted. Ignoring.")
else
raise LXC::Errors::ExecuteError, :command => command.inspect
end
end
end

# Return the output, making sure to replace any Windows-style
# newlines with Unix-style.
r.stdout.gsub("\r\n", "\n")
end

def raw(*command, &block)
int_callback = lambda do
@interrupted = true
@logger.info("Interrupted.")
end

# Append in the options for subprocess
command << { :notify => [:stdout, :stderr] }

Vagrant::Util::Busy.busy(int_callback) do
Vagrant::Util::Subprocess.execute(*command, &block)
end
end
end
end
end
Loading

0 comments on commit d62a053

Please sign in to comment.