Use Regex in registry_data_exists Resource in Chef Recipe - windows

I am creating a chef recipe and trying to create/update Registry key using registry_key resource. How I can use regex to validate the presence of the registry key and only update if it is necessary
registry_key'HKLM\\Software\\Microsoft\\WindowsNT\\CurrentVersion\\Winlogon' do
values [{
name: 'SCRemoveOption',
type: :string,
data: '1', }]
# recursive true
action :create
not_if { registry_data_exists?('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon',{ name: 'SCRemoveOption', type: :string, data: '1' } ,:x86_64) }
end
If the registry key exists with data 1 or 2 or 3, There is no need to udpate or else the registry_key-data should be updated to 1.
not_if { registry_data_exists?('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon',{ name: 'SCRemoveOption', type: :string, data: '([1-3])'} ,:x86_64) }

(Apologies for previous effort)
I have preferred using a powershell_script resource as I know PS syntax better than ruby/Chef. I used to check with Security Policy was enabled/disabled as follows:-
powershell_script 'my_script' do
guard_interpreter :powershell_script
cwd 'C:\Temp'
code <<-EOH
<...set the registry key(s)...>
EOH
not_if '(get-itemproperty HKLM:\\System\\CurrentControlSet\\Control\\Lsa).DisableDomainCreds -eq "1"'
end
Perhaps you can adapt to make it work with an array or values.

Related

List properties of a resource

I'm implementing a custom resource which is basically a facade to an existing resource (in the example below its the vault_certificate resource).
Using the existing resource this code is valid:
certificate = vault_certificate 'a common name' do
combine_certificate_and_chain true
output_certificates false # Just to decrease the chef-client run output
vault_path "pki/issue/#{node['deployment']}"
end
template "a path" do
source 'nginx/dummy.conf.erb'
variables(
certificate: certificate.certificate_filename
key: certificate.key_filename
)
end
Notice I can invoke certificate.certificate_filename or certificate.key_filename. Or more generally I can read any property defined by the vault_certificate resource.
Now with the new resource (sort of a facade to vault_certificate)
provides :vault_certificate_handle_exceptions
unified_mode true
property :common_name, String, name_property: true
property :max_retries, Integer, default: 5
action :create do
require 'retries'
# new_resource.max_retries is being used inside the retry_options. I omitted that part as its not relevant for the question
with_retries(retry_options) do
begin
vault_certificate new_resource.common_name do
combine_certificate_and_chain true
output_certificates false # Just to decrease the chef-client run output
vault_path "pki/issue/#{node['deployment']}"
ignore_failure :quiet
end
rescue Vault::HTTPClientError => e
data = JSON.parse(e.errors)['data']
if data['error'] == 'Certificate not found locally'
# This error is one we can recover from (actually we are expecting it). This raise with VaultCertificateError will trigger the with_retries.
raise VaultCertificateError.new("Waiting for the certificate to appear in the store (because I'm not the leader)", data)
else
# Any other error means something really went wrong.
raise e
end
end
end
end
If I now use this resource and try to invoke .certificate_filename or .key_filename:
certificate = vault_certificate_handle_exceptions 'a common name' do
action :create
end
template "a path" do
source 'nginx/dummy.conf.erb'
variables(
certificate: certificate.certificate_filename
key: certificate.key_filename
)
end
I get an error saying the method certificate_filename (or key_filename) is not defined for vault_certificate_handle_exceptions. To solve it I resorted to this hack:
provides :vault_certificate_handle_exceptions
unified_mode true
property :common_name, String, name_property: true
property :max_retries, Integer, default: 5
action :create do
require 'retries'
# new_resource.max_retries is being used inside the retry_options. I omitted that part as its not relevant for the question
with_retries(retry_options) do
begin
cert = vault_certificate new_resource.common_name do
combine_certificate_and_chain true
output_certificates false # Just to decrease the chef-client run output
vault_path "pki/issue/#{node['deployment']}"
ignore_failure :quiet
end
# These lines ensure we can read the vault_certificate properties as if they were properties of this resource (vault_certificate_handle_exceptions)
Chef::ResourceResolver.resolve(cert.resource_name).properties.keys.each do |name|
new_resource.send(:define_singleton_method, name.to_sym) do
cert.send(name.to_sym)
end
end
rescue Vault::HTTPClientError => e
data = JSON.parse(e.errors)['data']
if data['error'] == 'Certificate not found locally'
# This error is one we can recover from (actually we are expecting it). This raise with VaultCertificateError will trigger the with_retries.
raise VaultCertificateError.new("Waiting for the certificate to appear in the store (because I'm not the leader)", data)
else
# Any other error means something really went wrong.
raise e
end
end
end
end
Is there a cleaner way to achieve this? If not, is there a more direct way to list all the properties of a resource? I thought cert.properties would work, but no luck there.

Chef new_resource.<value> vs. <value>

I inherited a cookbook utilizing another cookbook with a custom resource.
I'm trying to figure out the use of the new_resource in this code.
In the code below, path new_resource.fullpath causes a "non-defined method."
When I changed that line to simply path fullpath to reference the local variable, it worked just fine. I can't figure out what the previous intent was.
I'm sure this is an easy one. Looking for an ELI5. Thanks!
Full code Block.
resource_name :mb_worker_role
default_action :create
property :service_name, String, name_property: true
property :root_path, String, required: true
property :executable, String, required: true
property :wcf_port, [Integer, String], default: 808
property :health_port, [Integer, String], default: 8000
property :use_dummy, [TrueClass, FalseClass], default: true
action :create do
# Create firewall allow for WCF port
windows_firewall_rule "#{new_resource.service_name} - WCF" do
localport new_resource.wcf_port.to_s
protocol 'TCP'
firewall_action :allow
end
# Create firewall allow for health port
windows_firewall_rule "#{new_resource.service_name} - Health" do
localport new_resource.health_port.to_s
protocol 'TCP'
firewall_action :allow
end
# Full path to service executable at root_path\executable
fullpath = "#{new_resource.root_path}\\#{new_resource.executable}"
# Create directory for worker role application root
directory new_resource.root_path do
recursive true
action :create
end
# Set NetTCPPortSharing to start on demand
windows_service 'NetTCPPortSharing' do
action :configure_startup
startup_type :manual
end
# Stage the dummy worker role executable if requested
# Only used to verify this resource when testing
if property_is_set?(:use_dummy)
cookbook_file 'Stage dummy worker role executable' do
# path new_resource.fullpath
path fullpath
cookbook 'mb_worker_role'
source 'WorkerRole.Default.exe'
only_if { new_resource.use_dummy }
action :create
end
end
# Create windows service if it does not exist
powershell_script "Installing Windows service #{new_resource.service_name}" do
code <<-EOH
# Create the Windows service
sc.exe create "#{new_resource.service_name}" binPath="#{fullpath}"
EOH
not_if "(Get-Service -Name #{new_resource.service_name}).Name -eq '#{new_resource.service_name}'"
end
# Set service to automatic and make sure it's running
windows_service new_resource.service_name do
action [:configure_startup, :enable, :start]
startup_type :automatic
end
end
path fullpath is a magical alias system that we actively discourage at this point. It was a nice thing to try, but the syntax has a lot of problems that result in unexpected behavior. Use new_resource.fullpath, far fewer footguns.

Chef Foodcritic rule not catching attribute strings

I have written a foodcritic rule to catch any attempt to write to a blacklist of directories/files under the /etc directory.
When blacklisted paths are passed to resource declarations as strings in a recipe, the rule triggers, however when they are passed as attributes, the rule does not trigger:
#resources = [
'file',
'template',
'remote_file',
'remote_directory',
'directory'
]
#blacklist = [
'/etc/ssh/',
'/etc/init',
...
]
rule 'RULE001', 'do not manipulate /etc other than init/,init.d/ & default/' do
tags %w(security)
recipe do |ast|
violations = []
#resources.each do |resource_type|
violations << find_resources(ast, type: resource_type).select do |resource|
res_str = (resource_attribute(resource, 'path' || resource_name(resource)).to_s
#blacklist.any? { |cmd| res_str.include? cmd }
end
end
violations.flatten
end
end
Testing this using the below, the literal strings are caught, however when passed as attributes they are passed. Can anyone see what I'm missing?
attributes/default.rb:
default['testbook']['etc-test'] = '/etc/ssh/test.conf'
default['testbook']['etc-dir-test'] = 'etc/ssh/somedir/'
recipes/default.rb:
#template '/etc/ssh/test.conf' do <-- caught
template node['testbook']['etc-test'] do #<-- not caught
source 'test.conf'
owner 'nobody'
group 'nobody'
mode '0644'
action :create
end
#directory '/etc/ssh/somedir' do <-- caught
directory node['testbook']['etc-dir-test'] do <-- not caught
action :create
end
Yes, this isn't something you can fully handle via static analysis. Foodcritic and tools like it can only handle things that are static in the code, anything that could vary at runtime won't be known.

Write users to .htpasswd in chef recipe

In a chef recipe invoked by chef-solo / vagrant I'm trying to write a .htpasswd file from an object of users.
I specify the users in vagrantfile like this...
chef.json = {
:apache => {
...
:my_users => {
:john => "test",
:matt => "test2"
}
...
My chef recipe looks like this at the moment:
file "/etc/apache2/.htpasswd" do
content "john:n5MfEoHOIQkKg"
owner "#{node['apache']['user']}"
group "#{node['apache']['group']}"
mode '0644'
action :create
end
As you can see I have hard coded John's credentials in there - however, I'm not a Ruby dev and I'm missing some very basic knowledge here...
How can I write all user credentials in the node['apache']['my_users'] attribute (defined in the chef.json) in a loop into the file while creating the password hash for each clear text password?
Note: I'm trying to avoid using a template for this simple file.
I got this working using the LWRP Charlie suggested.
First step is to modify the definition of users to be a proper array:
chef.json = {
:apache => {
...
:my_users => [
{ :username => "john", :password => "test1" },
{ :username => "matt", :password => "test2" }
]
...
I include the htpasswd dependency to metadata and bershelf.
Then in my recipe I create the users in a loop using the htpasswd call:
node[:apache][:my_users].each do |user|
htpasswd "/etc/apache2/.htpasswd" do
user user['username']
password user['password']
end
end
The htpasswd man page looks like it uses MD5 hashing on the passwords.
Perhaps you can generate md5 hashes in your recipe's Ruby code?
You can do it the native way, it requires htpasswd to be installed:
execute 'set password' do
sensitive true
command "htpasswd -cb /etc/htpasswd.users #{user} #{password}"
creates '/etc/htpasswd.users'
end
file '/etc/htpasswd.users' do
owner 'www-data'
group 'www-data'
mode 0o600
end

Chef template loop: can't convert Chef::Node::immutableMash into String

I've got a Vagrant setup in which I'm trying to use Chef-solo to generate an conf file which loops though defined variables to pass to the application. Everything is working except the loop and I'm not familiar enough with Ruby/Chef to spot the error.
I'm going to lay out the whole chain of events in case there is something along the way that is the problem, but the first portions of this process seem to work fine.
A config file is written in yaml and includes env variable definitions to be passed:
...
variables:
- DEBUG: 2
...
The config file is read in by the Vagrantfile into a ruby hash and used to create the Chef json nodes:
...
settings = YAML::load(File.read("config.yaml"))
# Provision The Virtual Machine Using Chef
config.vm.provision "chef_solo" do |chef|
chef.json = {
"mysql" => {"server_root_password" => "secret"},
"postgresql" => {"password" => {"postgres" => "secret"}},
"nginx" => {"pid" => "/run/nginx.pid"},
"php-fpm" => {"pid" => "/run/php5-fpm.pid"},
"databases" => settings["databases"] || [],
"sites" => settings["sites"] || [],
"variables" => settings["variables"] || []
}
...
A bunch of chef cookbooks are run (apt, php, nginx, mysql etc) and finally my custom cookbook which is whats giving me grief. The portion of the cookbook responsible for creating a the conf file is shown here:
# Configure All Of The Server Environment Variables
template "#{node['php-fpm']['pool_conf_dir']}/vars.conf" do
source "vars.erb"
owner "root"
group "root"
mode 0644
variables(
:vars => node['variables']
)
notifies :restart, "service[php-fpm]"
end
And the vars.erb is just a one-liner
<%= #vars.each {|key, value| puts "env[" + key + " = " + value } %>
So, when I run all this chef spits out an error about not being able to convert a hash to a string.
can't convert Chef::Node::immutableMash into String
So for some reason this is coming across as an immutableMash and the value of key ends up being the hash [{"DEBUG"=>2}] and value ends up a nil object, but I'm not sure why or how to correct it.
The hash is ending up as the value of key in your example because the YAML file declares DEBUG: 2 as a list member of variables. This translates to variables being an array with a single hash member.
Try changing the template code to this:
<%= #vars[0].each {|key, value| puts "env[" + key + " = " + value } %>
Or try changing the YAML to this and not changing the template code:
variables:
DEBUG: 2
Either change will get your template loop iterating over the hash that you are expecting.

Resources