Merge branch 'master' of https://github.com/servebox/vagrant-lxc
This commit is contained in:
commit
251dec55fe
7 changed files with 189 additions and 100 deletions
|
@ -6,5 +6,9 @@ module Vagrant
|
||||||
def self.source_root
|
def self.source_root
|
||||||
@source_root ||= Pathname.new(File.dirname(__FILE__)).join('..').expand_path
|
@source_root ||= Pathname.new(File.dirname(__FILE__)).join('..').expand_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.sudo_wrapper_path
|
||||||
|
"/usr/local/bin/vagrant-lxc-wrapper-#{VERSION}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,13 +4,20 @@ module Vagrant
|
||||||
module LXC
|
module LXC
|
||||||
module Command
|
module Command
|
||||||
class Sudoers < Vagrant.plugin("2", :command)
|
class Sudoers < Vagrant.plugin("2", :command)
|
||||||
|
|
||||||
|
def initialize(argv, env)
|
||||||
|
super
|
||||||
|
@argv
|
||||||
|
@env = env
|
||||||
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
options = { user: ENV['USER'] }
|
options = { user: ENV['USER'] }
|
||||||
|
|
||||||
opts = OptionParser.new do |opts|
|
opts = OptionParser.new do |opts|
|
||||||
opts.banner = "Usage: vagrant lxc sudoers"
|
opts.banner = "Usage: vagrant lxc sudoers"
|
||||||
opts.separator ""
|
opts.separator ""
|
||||||
opts.on('-u', '--user', "The user for which to create the policy (defaults to '#{options[:user]}')") do |u|
|
opts.on('-u user', '--user user', String, "The user for which to create the policy (defaults to '#{options[:user]}')") do |u|
|
||||||
options[:user] = u
|
options[:user] = u
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -18,68 +25,165 @@ module Vagrant
|
||||||
argv = parse_options(opts)
|
argv = parse_options(opts)
|
||||||
return unless argv
|
return unless argv
|
||||||
|
|
||||||
filename = "vagrant-lxc-#{options[:user]}"
|
wrapper_path = Vagrant::LXC.sudo_wrapper_path
|
||||||
to_sudoers!(create_tempfile!(options[:user], filename), filename)
|
wrapper = create_wrapper!
|
||||||
|
sudoers = create_sudoers!(options[:user], wrapper_path)
|
||||||
|
|
||||||
|
su_copy([
|
||||||
|
{source: wrapper, target: wrapper_path, mode: "0555"},
|
||||||
|
{source: sudoers, target: sudoers_path, mode: "0440"}
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
def sudoers_path
|
||||||
|
"/etc/sudoers.d/vagrant-lxc-#{Vagrant::LXC::VERSION.gsub( /\./, '-')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
# REFACTOR: Make use ERB rendering after https://github.com/mitchellh/vagrant/issues/3231
|
||||||
|
# lands into core
|
||||||
|
def create_wrapper!
|
||||||
|
wrapper = Tempfile.new('lxc-wrapper').tap do |file|
|
||||||
|
file.puts "#!/usr/bin/env ruby"
|
||||||
|
file.puts "# Automatically created by vagrant-lxc"
|
||||||
|
file.puts <<-EOF
|
||||||
|
class Whitelist
|
||||||
|
class << self
|
||||||
|
def add(command, *args)
|
||||||
|
list[command] << args
|
||||||
|
end
|
||||||
|
|
||||||
|
def list
|
||||||
|
@list ||= Hash.new do |key, hsh|
|
||||||
|
key[hsh] = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed(command)
|
||||||
|
list[command] || []
|
||||||
|
end
|
||||||
|
|
||||||
|
def run!(argv)
|
||||||
|
begin
|
||||||
|
command, args = `which \#{argv.shift}`.chomp, argv || []
|
||||||
|
check!(command, args)
|
||||||
|
puts `\#{command} \#{args.join(" ")}`
|
||||||
|
exit $?.to_i
|
||||||
|
rescue => e
|
||||||
|
STDERR.puts e.message
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def check!(command, args)
|
||||||
|
allowed(command).each do |checks|
|
||||||
|
return if valid_args?(args, checks)
|
||||||
|
end
|
||||||
|
raise_invalid(command, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_args?(args, checks)
|
||||||
|
return false unless valid_length?(args, checks)
|
||||||
|
check = nil
|
||||||
|
args.each_with_index do |provided, i|
|
||||||
|
check = checks[i] unless check == '**'
|
||||||
|
return false unless match?(provided, check)
|
||||||
|
end
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_length?(args, checks)
|
||||||
|
args.length == checks.length || checks.last == '**'
|
||||||
|
end
|
||||||
|
|
||||||
|
def match?(arg, check)
|
||||||
|
check == '**' || check.is_a?(Regexp) && !!check.match(arg) || arg == check
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_invalid(command, args)
|
||||||
|
raise "Invalid arguments for command \#{command}, " <<
|
||||||
|
"provided args: \#{args.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
base = "/var/lib/lxc"
|
||||||
|
base_path = %r{\\A\#{base}/.*\\z}
|
||||||
|
templates_path = %r{\\A/usr/(share|lib|lib64|local/lib)/lxc/templates/.*\\z}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Commands from provider.rb
|
||||||
|
# - Check lxc is installed
|
||||||
|
Whitelist.add '/usr/bin/which', /\\Alxc-\\w+\\z/
|
||||||
|
|
||||||
|
##
|
||||||
|
# Commands from driver.rb
|
||||||
|
# - Container config file
|
||||||
|
Whitelist.add '/bin/cat', base_path
|
||||||
|
# - Shared folders
|
||||||
|
Whitelist.add '/bin/mkdir', '-p', base_path
|
||||||
|
# - Container config customizations and pruning
|
||||||
|
Whitelist.add '/bin/cp', '-f', %r{/tmp/.*}, base_path
|
||||||
|
Whitelist.add '/bin/chown', 'root:root', base_path
|
||||||
|
# - Template import
|
||||||
|
Whitelist.add '/bin/cp', %r{\\A.*\\z}, templates_path
|
||||||
|
Whitelist.add '/bin/cp', %r{\\A.*\\z}, templates_path
|
||||||
|
Whitelist.add '/bin/cp', %r{\\A.*\\z}, templates_path
|
||||||
|
Whitelist.add '/bin/chmod', '+x', templates_path
|
||||||
|
# - Template removal
|
||||||
|
Whitelist.add '/bin/rm', templates_path
|
||||||
|
# - Packaging
|
||||||
|
Whitelist.add '/bin/tar', '--numeric-owner', '-cvzf', %r{/tmp/.*/rootfs.tar.gz}, '-C', base_path, './rootfs'
|
||||||
|
Whitelist.add '/bin/chown', /\\A\\d+:\\d+\\z/, %r{\\A/tmp/.*/rootfs\.tar\.gz\\z}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Commands from driver/cli.rb
|
||||||
|
Whitelist.add '/usr/bin/lxc-version'
|
||||||
|
Whitelist.add '/usr/bin/lxc-ls'
|
||||||
|
Whitelist.add '/usr/bin/lxc-info', '--name', /.*/
|
||||||
|
Whitelist.add '/usr/bin/lxc-create', '--template', /.*/, '--name', /.*/, '**'
|
||||||
|
Whitelist.add '/usr/bin/lxc-destroy', '--name', /.*/
|
||||||
|
Whitelist.add '/usr/bin/lxc-start', '-d', '--name', /.*/, '**'
|
||||||
|
Whitelist.add '/usr/bin/lxc-stop', '--name', /.*/
|
||||||
|
Whitelist.add '/usr/bin/lxc-shutdown', '--name', /.*/
|
||||||
|
Whitelist.add '/usr/bin/lxc-attach', '--name', /.*/, '**'
|
||||||
|
Whitelist.add '/usr/bin/lxc-attach', '-h'
|
||||||
|
|
||||||
|
##
|
||||||
|
# Commands from driver/action/remove_temporary_files.rb
|
||||||
|
Whitelist.add '/bin/rm', '-rf', %r{\\A\#{base}/.*/rootfs/tmp/.*}
|
||||||
|
|
||||||
|
# Watch out for stones
|
||||||
|
Whitelist.run!(ARGV)
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
wrapper.close
|
||||||
|
wrapper.path
|
||||||
|
end
|
||||||
|
|
||||||
# REFACTOR: Make use ERB rendering after https://github.com/mitchellh/vagrant/issues/3231
|
# REFACTOR: Make use ERB rendering after https://github.com/mitchellh/vagrant/issues/3231
|
||||||
# lands into core
|
# lands into core
|
||||||
def create_tempfile!(user, filename)
|
def create_sudoers!(user, command)
|
||||||
sudoers = Tempfile.new(filename).tap do |file|
|
sudoers = Tempfile.new('vagrant-lxc-sudoers').tap do |file|
|
||||||
file.write "# Automatically created by vagrant-lxc\n"
|
file.puts "# Automatically created by vagrant-lxc"
|
||||||
commands.each do |command|
|
file.puts "Cmnd_Alias LXC = #{command}"
|
||||||
file.write sudoers_policy(user, command[:cmd], command[:args])
|
file.puts "#{user} ALL=(root) NOPASSWD: LXC"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
sudoers.close
|
sudoers.close
|
||||||
File.chmod(0644, sudoers.path)
|
|
||||||
sudoers.path
|
sudoers.path
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_sudoers!(source, destination)
|
def su_copy(files)
|
||||||
destination = "/etc/sudoers.d/#{destination}"
|
commands = files.map { |file|
|
||||||
commands = [
|
|
||||||
"rm -f #{destination}",
|
|
||||||
"cp #{source} #{destination}",
|
|
||||||
"chmod 440 #{destination}"
|
|
||||||
]
|
|
||||||
`echo "#{commands.join('; ')}" | sudo sh`
|
|
||||||
end
|
|
||||||
|
|
||||||
def sudoers_policy(user, command, args)
|
|
||||||
vagrant_home = "#{`echo ~#{user}`.chomp}/.vagrant.d"
|
|
||||||
args = args.gsub /%\{VAGRANT_D\}/, vagrant_home
|
|
||||||
args = args.gsub /%\{BOXES\}/, "#{vagrant_home}/boxes"
|
|
||||||
"#{user} ALL=(root) NOPASSWD: #{command} #{args}\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def commands
|
|
||||||
[
|
[
|
||||||
{ cmd: '/usr/bin/lxc-ls', args: '' },
|
"rm -f #{file[:target]}",
|
||||||
{ cmd: '/usr/bin/lxc-info', args: '' },
|
"cp #{file[:source]} #{file[:target]}",
|
||||||
{ cmd: '/usr/bin/lxc-attach', args: '' },
|
"chown root:root #{file[:target]}",
|
||||||
{ cmd: '/usr/bin/which', args: 'lxc-*' },
|
"chmod #{file[:mode]} #{file[:target]}"
|
||||||
{ cmd: '/bin/cat', args: '/var/lib/lxc/*' },
|
|
||||||
{ cmd: '/bin/mkdir', args: '-p /var/lib/lxc/*' },
|
|
||||||
{ cmd: '/bin/su', args: "root -c sed -e '*' -ibak /var/lib/lxc/*" },
|
|
||||||
{ cmd: '/bin/su', args: "root -c echo '*' >> /var/lib/lxc/*" },
|
|
||||||
{ cmd: '/usr/bin/lxc-start', args: '-d --name *' },
|
|
||||||
{ cmd: '/bin/cp', args: '%{BOXES}/*/lxc/lxc-template /usr/lib/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/cp', args: '%{VAGRANT_D}/gems/gems/vagrant-lxc-*/scripts/lxc-template /usr/lib/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/cp', args: '%{BOXES}/*/lxc/lxc-template /usr/share/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/cp', args: '%{VAGRANT_D}/gems/gems/vagrant-lxc-*/scripts/lxc-template /usr/share/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/rm', args: '/usr/lib/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/rm', args: '/usr/share/lxc/templates/*' },
|
|
||||||
{ cmd: '/bin/chmod', args: '+x /usr/lib/lxc/*' },
|
|
||||||
{ cmd: '/bin/chmod', args: '+x /usr/share/lxc/*' },
|
|
||||||
{ cmd: '/usr/bin/lxc-create', args: '--template * --name * -- --tarball %{BOXES}/*' },
|
|
||||||
{ cmd: '/bin/rm', args: '-rf /var/lib/lxc/*/rootfs/tmp/*' },
|
|
||||||
{ cmd: '/usr/bin/lxc-shutdown', args: '--name *' },
|
|
||||||
{ cmd: '/usr/bin/lxc-stop', args: '--name *' },
|
|
||||||
{ cmd: '/usr/bin/lxc-destroy', args: '--name *' }
|
|
||||||
]
|
]
|
||||||
|
}.flatten
|
||||||
|
system "echo \"#{commands.join("; ")}\" | sudo sh"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,12 +6,6 @@ module Vagrant
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
attr_reader :customizations
|
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
|
|
||||||
|
|
||||||
# A string to explicitly set the container name. To use the vagrant
|
# A string to explicitly set the container name. To use the vagrant
|
||||||
# machine name, set this to :machine
|
# machine name, set this to :machine
|
||||||
attr_accessor :container_name
|
attr_accessor :container_name
|
||||||
|
@ -41,21 +35,6 @@ module Vagrant
|
||||||
@sudo_wrapper = nil if @sudo_wrapper == UNSET_VALUE
|
@sudo_wrapper = nil if @sudo_wrapper == UNSET_VALUE
|
||||||
@container_name = nil if @container_name == UNSET_VALUE
|
@container_name = nil if @container_name == UNSET_VALUE
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -121,17 +121,8 @@ module Vagrant
|
||||||
target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
|
target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
|
||||||
|
|
||||||
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
|
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
|
||||||
# "vagrant package" will copy the existing lxc-template in the new box file
|
@sudo_wrapper.run('tar', '--numeric-owner', '-cvzf', target_path, '-C',
|
||||||
# To keep this function backwards compatible with existing boxes, the path
|
rootfs_path.parent.to_s, "./#{rootfs_path.basename.to_s}")
|
||||||
# included in the tarball needs to have the same amount of path components (2)
|
|
||||||
# that will be stripped before extraction, hence the './.'
|
|
||||||
# TODO: This should be reviewed before 1.0
|
|
||||||
cmds = [
|
|
||||||
"cd #{base_path}",
|
|
||||||
"rm -f rootfs.tar.gz",
|
|
||||||
"tar --numeric-owner -czf #{target_path} -C #{rootfs_path} './.'"
|
|
||||||
]
|
|
||||||
@sudo_wrapper.su_c(cmds.join(' && '))
|
|
||||||
|
|
||||||
@logger.info "Changing rootfs tarball owner"
|
@logger.info "Changing rootfs tarball owner"
|
||||||
user_details = Etc.getpwnam(Etc.getlogin)
|
user_details = Etc.getpwnam(Etc.getlogin)
|
||||||
|
@ -149,7 +140,9 @@ module Vagrant
|
||||||
def prune_customizations
|
def prune_customizations
|
||||||
# Use sed to just strip out the block of code which was inserted by Vagrant
|
# Use sed to just strip out the block of code which was inserted by Vagrant
|
||||||
@logger.debug 'Prunning vagrant-lxc customizations'
|
@logger.debug 'Prunning vagrant-lxc customizations'
|
||||||
@sudo_wrapper.su_c("sed -e '/^# VAGRANT-BEGIN/,/^# VAGRANT-END/ d' -ibak #{base_path.join('config')}")
|
contents = config_string
|
||||||
|
config_string.gsub! /^# VAGRANT-BEGIN(.|\s)*# VAGRANT-END/, ''
|
||||||
|
write_config(contents)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
@ -160,10 +153,23 @@ module Vagrant
|
||||||
end
|
end
|
||||||
customizations.unshift '# VAGRANT-BEGIN'
|
customizations.unshift '# VAGRANT-BEGIN'
|
||||||
customizations << '# VAGRANT-END'
|
customizations << '# VAGRANT-END'
|
||||||
|
contents = config_string
|
||||||
|
|
||||||
config_file = base_path.join('config').to_s
|
config_file = base_path.join('config').to_s
|
||||||
customizations.each do |line|
|
customizations.each do |line|
|
||||||
@sudo_wrapper.su_c("echo '#{line}' >> #{config_file}")
|
contents << line
|
||||||
|
contents << "\n"
|
||||||
|
end
|
||||||
|
write_config(contents)
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_config(contents)
|
||||||
|
Tempfile.new('lxc-config').tap do |file|
|
||||||
|
file.chmod 0644
|
||||||
|
file.write contents
|
||||||
|
file.close
|
||||||
|
@sudo_wrapper.run 'cp', '-f', file.path, base_path.join('config').to_s
|
||||||
|
@sudo_wrapper.run 'chown', 'root:root', base_path.join('config').to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,9 @@ module Vagrant
|
||||||
|
|
||||||
def sudo_wrapper
|
def sudo_wrapper
|
||||||
@shell ||= begin
|
@shell ||= begin
|
||||||
wrapper = @machine.provider_config.sudo_wrapper
|
wrapper = Pathname.new(LXC.sudo_wrapper_path).exist? &&
|
||||||
wrapper = Pathname(wrapper).expand_path(@machine.env.root_path).to_s if wrapper
|
LXC.sudo_wrapper_path || nil
|
||||||
|
@logger.debug("Found sudo wrapper : #{wrapper}") if wrapper
|
||||||
SudoWrapper.new(wrapper)
|
SudoWrapper.new(wrapper)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,20 +10,11 @@ module Vagrant
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(*command)
|
def run(*command)
|
||||||
command.unshift @wrapper_path if @wrapper_path
|
options = command.last.is_a?(Hash) ? command.last : {}
|
||||||
|
command.unshift @wrapper_path if @wrapper_path && !options[:no_wrapper]
|
||||||
execute *(['sudo'] + command)
|
execute *(['sudo'] + command)
|
||||||
end
|
end
|
||||||
|
|
||||||
def su_c(command)
|
|
||||||
su_command = if @wrapper_path
|
|
||||||
"#{@wrapper_path} \"#{command}\""
|
|
||||||
else
|
|
||||||
"su root -c \"#{command}\""
|
|
||||||
end
|
|
||||||
@logger.debug "Running 'sudo #{su_command}'"
|
|
||||||
system "sudo #{su_command}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# TODO: Review code below this line, it was pretty much a copy and
|
# TODO: Review code below this line, it was pretty much a copy and
|
||||||
|
|
|
@ -75,11 +75,15 @@ describe Vagrant::LXC::Driver do
|
||||||
let(:customizations) { [['a', '1'], ['b', '2']] }
|
let(:customizations) { [['a', '1'], ['b', '2']] }
|
||||||
let(:internal_customization) { ['internal', 'customization'] }
|
let(:internal_customization) { ['internal', 'customization'] }
|
||||||
let(:cli) { double(Vagrant::LXC::Driver::CLI, start: true) }
|
let(:cli) { double(Vagrant::LXC::Driver::CLI, start: true) }
|
||||||
let(:sudo) { double(Vagrant::LXC::SudoWrapper, su_c: true) }
|
let(:sudo) { double(Vagrant::LXC::SudoWrapper) }
|
||||||
|
|
||||||
subject { described_class.new('name', sudo, cli) }
|
subject { described_class.new('name', sudo, cli) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
sudo.should_receive(:run).with('cat', '/var/lib/lxc/name/config').exactly(3).times.
|
||||||
|
and_return('# CONFIGURATION')
|
||||||
|
sudo.should_receive(:run).twice.with('cp', '-f', %r{/tmp/.*}, '/var/lib/lxc/name/config')
|
||||||
|
sudo.should_receive(:run).twice.with('chown', 'root:root', '/var/lib/lxc/name/config')
|
||||||
subject.customizations << internal_customization
|
subject.customizations << internal_customization
|
||||||
subject.start(customizations)
|
subject.start(customizations)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue