Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for deleting hash tables #1

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 76 additions & 22 deletions lib/puppet/provider/gpo/lgpo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def exists?
@property_hash[:ensure] == :present
end

def deleted?
@property_hash[:ensure] == :deleted
end

def create
set_value(resource[:value])
@property_hash[:ensure] = :present
Expand Down Expand Up @@ -51,14 +55,19 @@ def self.instances
['machine', 'user'].map do |scope|
pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol"
next [] unless File.file?(pol_file)

resources = Hash.new

gpos = lgpo('/parse', '/q', "/#{scope[0]}", pol_file)
gpos.split("\n\n").reject { |l| l.start_with? ';' }.map do |g|
split_g = g.split("\n")
path = paths.get_by_key(scope, split_g[1].downcase, split_g[2].downcase)

if path.nil?
warn "Unkown path for gpo resource: '#{split_g[1]}/#{split_g[2]}'"
next
end

admx_file = path['admx_file'].downcase
policy_id = path['policy_id'].downcase
setting_valuename = path['setting_valuename'].downcase
Expand Down Expand Up @@ -92,10 +101,31 @@ def self.instances
:value => value,
}
end

end
resources.map{|k, v| new(v)}
end.flatten
end.flatten
end

def out_line(scope, key, value_name, value)
"#{scope}\n#{key}\n#{value_name}\n#{value}"
end

# Convert lgpo_import.txt to lgpo_import.pol with lgpo.exe
def convert_to_pol(file)
pol_file = file.sub(/\.txt$/,'.pol')
lgpo_args = ['/r', file, '/w', pol_file]
lgpo(*lgpo_args)
File.delete(file)
pol_file
end

# import lgpo_import.pol with lgpo.exe
def import_pol(file, scope, guid)
lgpo_args = ["/#{scope[0]}", file]
lgpo_args << '/e' << guid if guid
lgpo(*lgpo_args)
File.delete(file)
end

def set_value(val)
Expand All @@ -110,32 +140,56 @@ def set_value(val)
raise Puppet::Error, "Wrong path: '#{path}'"
end

out_scope = scope == 'machine' ? 'computer' : scope

delete_value = setting_valuetype == '[HASHTABLE]' ? 'DELETEALLVALUES' : 'DELETE'
out_scope = (scope == 'machine' ? 'computer' : scope).capitalize

real_val = val == 'DELETE' ? delete_value : "#{path['setting_valuetype'].gsub('REG_', '')}:#{val}"
setting_valuename = real_val == 'DELETEALLVALUES' ? '*' : path['setting_valuename']
out = Array.new
if setting_valuetype == '[HASHTABLE]'
if val == 'DELETE'
out << out_line(out_scope, path['setting_key'], '*', 'DELETEALLVALUES')
else
val.each do |k, v|
out << out_line(out_scope, path['setting_key'], k, "SZ:#{v}")
end
end
else
val = "#{path['setting_valuetype'].gsub('REG_', '')}:#{val}" unless val == 'DELETE'
out << out_line(out_scope, path['setting_key'], path['setting_valuename'], val)
end

out = "#{out_scope}\n#{path['setting_key']}\n#{setting_valuename}\n#{real_val}"
# If it is a hash table we need to delete the current values in the pol file so that they can be updated
remove_key(path['setting_key'], scope, path['policy_cse']) if setting_valuetype == '[HASHTABLE]'

out_file_path = File.join(Puppet[:vardir], 'lgpo_import.txt')
out_polfile_path = File.join(Puppet[:vardir], 'lgpo_import.pol')
File.open(out_file_path, 'w') do |out_file|
out_file.write(out)
out_file.write(out.join("\n\n"))
end

out_polfile_path = convert_to_pol(out_file_path)
import_pol(out_polfile_path, scope, path['policy_cse'])
end

# Convert lgpo_import.txt to lgpo_import.pol with lgpo.exe
lgpo_args = ['/r', out_file_path, '/w', out_polfile_path]
lgpo(*lgpo_args)
File.delete(out_file_path)
def remove_key(key, scope, cse_guid)
# This function is removing content from the pol file so that hash values can be updated without carying over the old part
Puppet.debug "remove #{key} in scope #{scope}"

system_pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol"
return unless File.file?(system_pol_file)

out_file = File.join(Puppet[:vardir], 'lgpo_import.txt')
gpos = lgpo('/parse', '/q', "/#{scope[0]}", system_pol_file)
# Parse file and remove key
new_gpos = gpos.split("\n\n").reject { |l| l.start_with? ';' }
.reject{ |l| l.split("\n")[1] == key }
File.write(out_file, new_gpos.join("\n\n"))

# convert txt file to pol file
pol_file = convert_to_pol(out_file)

# import lgpo_import.pol with lgpo.exe
lgpo_args = ["/#{scope[0]}", out_polfile_path]
if guid = path['policy_cse']
lgpo_args << '/e' << guid
end
lgpo(*lgpo_args)
File.delete(out_polfile_path)
# delete existing polfile
File.delete(system_pol_file)

# apply pol file to
import_pol(pol_file, scope, cse_guid)
end
end

5 changes: 5 additions & 0 deletions lib/puppet/type/gpo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
newvalue(:deleted) do
provider.delete
end

def insync?(is)
return true if should == :deleted and provider.deleted?
super
end
end

newparam(:name, :namevar => true) do
Expand Down
91 changes: 76 additions & 15 deletions spec/unit/puppet/provider/gpo/lgpo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,45 @@ def stub_create(scope, content, cse)
expect(File).to receive(:delete).once.with(out_polfile).and_return(nil)
end

def stub_hash_delete(scope, content, cse)
# This is the initial write to the gpo_import_file which writes the deleteallvalues statement for a hashed instance
lgpo_import_file = StringIO.new
expect(File).to receive(:open).once.with(out_file, 'w').and_yield(lgpo_import_file)
expect(lgpo_import_file).to receive(:write).with(content)

# pol file should get read one time initiated by the lgpo.exe call
pol_file = "C:\\Windows\\System32\\GroupPolicy\\#{scope.capitalize}\\Registry.pol"
allow(File).to receive(:file?) # Catch all calls
expect(File).to receive(:file?).once.with(pol_file).and_return(true)

# the stub for the pol lgpo call needs to be added here in order to simulate output
if true
provider.class.expects(:lgpo).once.with('/parse', '/q', "/#{scope[0]}", pol_file)
.returns(File.read(File.join(
File.dirname(__FILE__),
"../../../../fixtures/unit/puppet/provider/gpo/lgpo/#{scope}/full.out")))
else
provider.class.expects(:lgpo).never
end

# This is the subsequent writes to the lgpo file with the filtered content from the parsing
expect(File).to receive(:open).once.with(out_file, 'a').and_yield(lgpo_import_file)
expect(lgpo_import_file).to receive(:write).at_least(:once)

args = ["/r", out_file]
args << '/w' << out_polfile
provider.class.expects(:lgpo).once.with(*args).returns(nil)
expect(File).to receive(:delete).once.with(out_file).and_return(nil)

# Polfile needs to be deleted so a fresh import can be done from the filtered lgpo file
expect(File).to receive(:delete).once.with(pol_file).and_return(nil)

args = ["/#{scope[0]}", out_polfile]
args << '/e' << cse unless cse.nil?
provider.class.expects(:lgpo).once.with(*args).returns(nil)
expect(File).to receive(:delete).once.with(out_polfile).and_return(nil)
end

context 'when listing instances' do
context 'when the gpo file exists' do
it 'should list instances' do
Expand Down Expand Up @@ -175,7 +214,7 @@ def stub_create(scope, content, cse)

context 'when there is no cse' do
it 'should create a resource without /e' do
stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDWORD:1", nil)
stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDWORD:1", nil)

provider.create
end
Expand All @@ -191,8 +230,25 @@ def stub_create(scope, content, cse)
end

it 'should create a resource with /e' do
stub_create('machine', "computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDWORD:1", '{D76B9641-3288-4f75-942D-087DE603E3EA}')
stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDWORD:1", '{D76B9641-3288-4f75-942D-087DE603E3EA}')

provider.create
end
end

context 'when resource contain a hash value' do
let(:params) do
{
:title => 'windowsdefender::exclusions_processes::exclusions_processeslist',
:value => {'c:\windows\process0.exe' => '0', 'c:\windows\process1.exe' => '0',},
:provider => 'lgpo',
}
end

it 'should create two entries in LGPO import file' do
stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\nc:\\windows\\process0.exe\nSZ:0\n\nComputer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\nc:\\windows\\process1.exe\nSZ:0", nil)
pol_file = "C:\\Windows\\System32\\GroupPolicy\\Machine\\Registry.pol"
expect(File).to receive(:delete).once.with(pol_file).and_return(nil)
provider.create
end
end
Expand All @@ -205,38 +261,43 @@ def stub_create(scope, content, cse)

context 'when there is no cse' do
it 'should create a resource without /e' do
stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDELETE", nil)
stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU\nAllowMUUpdateService\nDELETE", nil)

provider.delete
end
end

context 'when we need to delete a HASHTABLE instance' do
context 'when there is a cse' do
let(:params) do
{
:title => 'machine::windowsdefender::exclusions_processes::exclusions_processeslist',
:ensure => :deleted,
:title => 'admpwd::pol_admpwd_enabled::admpwdenabled',
:value => '1',
:provider => 'lgpo',
}
end
it 'should create a resource without /e' do
stub_create('machine', "computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\n*\nDELETEALLVALUES", nil)

it 'should create a resource with /e' do
stub_create('machine', "Computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDELETE", '{D76B9641-3288-4f75-942D-087DE603E3EA}')

provider.delete
end
end

context 'when there is a cse' do
end
context 'when deleting a hash resource' do
before :each do
expect(Puppet).to receive(:[]).exactly(3).times.with(:vardir).and_return('C:\ProgramData\PuppetLabs\Puppet\var')
end

context 'when we need to delete a HASHTABLE instance' do
let(:params) do
{
:title => 'admpwd::pol_admpwd_enabled::admpwdenabled',
:value => '1',
:title => 'machine::windowsdefender::exclusions_processes::exclusions_processeslist',
:ensure => :deleted,
:provider => 'lgpo',
}
end

it 'should create a resource with /e' do
stub_create('machine', "computer\nSoftware\\Policies\\Microsoft Services\\AdmPwd\nAdmPwdEnabled\nDELETE", '{D76B9641-3288-4f75-942D-087DE603E3EA}')
it 'should create a resource without /e' do
stub_hash_delete('machine', "Computer\nSoftware\\Policies\\Microsoft\\Windows Defender\\Exclusions\\Processes\n*\nDELETEALLVALUES", nil)

provider.delete
end
Expand Down