puppet-3.4.3/ 0000755 0052762 0001160 00000000000 12300765640 012731 5 ustar jenkins jenkins puppet-3.4.3/examples/ 0000755 0052762 0001160 00000000000 12300765640 014547 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/ 0000755 0052762 0001160 00000000000 12300765640 015637 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/site.pp 0000644 0052762 0001160 00000000054 12300765631 017143 0 ustar jenkins jenkins node default {
hiera_include('classes')
}
puppet-3.4.3/examples/hiera/README.md 0000644 0052762 0001160 00000010121 12300765631 017111 0 ustar jenkins jenkins A working demo of Hiera with YAML and Puppet backends.
======================================================
This demo consists of:
* A NTP module that has defaults for pool.ntp.org servers
* A common data module where module users can create override data in pp files
* A YAML data source in etc/hieradb where users can override data in yaml files
* A couple of users modules that just notify the fact that they are being included
* In Hiera data files a key called _classes_ that decides what to include on a node
Below various usage scenarios can be tested using this module.
The examples below assume you have Hiera already installed and that you have
hiera-puppet cloned from github and running these commands in _hiera-puppet/example_ as cwd.
Module from forge with module defaults
--------------------------------------
* Move the _modules/data_ directory to _modules/data.bak_ to avoid overrides
used further in the example
* Run puppet, creates _/etc/ntp.conf_ with ntp.org addresses
* The _hiera\_include()_ function includes just _users::common_
$ mv modules/data modules/data.bak
$ puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/ensure: defined content as '{md5}7045121976147a932a66c7671939a9ad'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server 1.pool.ntp.org
server 2.pool.ntp.org
Site wide override data in _data::common_
-----------------------------------------
* Restore the _modules/data_ directory that has a class _data::common_ that declares site wide overrides
* The _hiera_include()_ function includes just _users::common_
$ mv modules/data.bak modules/data
$ puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}7045121976147a932a66c7671939a9addc2' to '{md5}8f9039fe1989a278a0a8e1836acb8d23'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server ntp1.example.com
server ntp2.example.com
Fact driven overrides for location=dc1
--------------------------------------
* Set a fact location=dc1 that uses the YAML data in _etc/hieradb/dc1.yaml_ to override
* Show that machines in dc2 would use site-wide defaults
* The _hiera_include()_ function includes _users::common_ and _users::dc1_ as the data file for dc1 adds that
$ FACTER_location=dc1 puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}8f9039fe1989a278a0a8e1836acb8d23' to '{md5}074d0e2ac727f6cb9afe3345d574b578'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
notice: /Stage[main]/Users::Dc1/Notify[Adding users::dc1]/message: defined 'message' as 'Adding users::dc1'
$ cat /tmp/ntp.conf
server ntp1.dc1.example.com
server ntp2.dc1.example.com
Now simulate a machine in _dc2_, because there is no data for dc2 it uses the site wide defaults and
does not include the _users::dc1_ class anymore
$ FACTER_location=dc2 puppet apply --config etc/puppet.conf site.pp
warning: Could not find class data::dc2 for nephilim.ml.org
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}074d0e2ac727f6cb9afe3345d574b578' to '{md5}8f9039fe1989a278a0a8e1836acb8d23'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server ntp1.example.com
server ntp2.example.com
You could create override data in the following places for a machine in _location=dc2_, they will be searched in this order and the first one with data will match.
* file etc/hieradb/dc2.yaml
* file etc/hieradb/common.yaml
* class data::dc2
* class data::production
* class data::common
* class ntp::config::data
* class ntp::data
In this example due to the presence of _common.yaml_ that declares _ntpservers_ the classes will never be searched, it will have precedence.
puppet-3.4.3/examples/hiera/etc/ 0000755 0052762 0001160 00000000000 12300765640 016412 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/etc/puppet.conf 0000644 0052762 0001160 00000000037 12300765631 020576 0 ustar jenkins jenkins [main]
modulepath = modules
puppet-3.4.3/examples/hiera/etc/hieradb/ 0000755 0052762 0001160 00000000000 12300765640 020010 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/etc/hieradb/development.yaml 0000644 0052762 0001160 00000000040 12300765631 023210 0 ustar jenkins jenkins ---
classes: users::development
puppet-3.4.3/examples/hiera/etc/hieradb/dc1.yaml 0000644 0052762 0001160 00000000132 12300765631 021337 0 ustar jenkins jenkins ---
ntpservers:
- ntp1.dc1.example.com
- ntp2.dc1.example.com
classes:
- users::dc1
puppet-3.4.3/examples/hiera/etc/hieradb/common.yaml 0000644 0052762 0001160 00000000053 12300765631 022162 0 ustar jenkins jenkins classes:
- users::common
- ntp::config
puppet-3.4.3/examples/hiera/etc/hiera.yaml 0000644 0052762 0001160 00000000240 12300765631 020362 0 ustar jenkins jenkins ---
:backends:
- yaml
- puppet
:hierarchy:
- "%{location}"
- "%{environment}"
- common
:yaml:
:datadir: etc/hieradb
:puppet:
:datasource: data
puppet-3.4.3/examples/hiera/modules/ 0000755 0052762 0001160 00000000000 12300765640 017307 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/ntp/ 0000755 0052762 0001160 00000000000 12300765640 020110 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/ntp/templates/ 0000755 0052762 0001160 00000000000 12300765640 022106 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/ntp/templates/ntp.conf.erb 0000644 0052762 0001160 00000000115 12300765631 024322 0 ustar jenkins jenkins <% [ntpservers].flatten.each do |server| -%>
server <%= server %>
<% end -%>
puppet-3.4.3/examples/hiera/modules/ntp/manifests/ 0000755 0052762 0001160 00000000000 12300765640 022101 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/ntp/manifests/config.pp 0000644 0052762 0001160 00000000317 12300765631 023710 0 ustar jenkins jenkins # lookup ntpservers from hiera, or allow user of class to provide other value
class ntp::config($ntpservers = hiera('ntpservers')) {
file{'/tmp/ntp.conf':
content => template('ntp/ntp.conf.erb')
}
}
puppet-3.4.3/examples/hiera/modules/ntp/manifests/data.pp 0000644 0052762 0001160 00000000204 12300765631 023347 0 ustar jenkins jenkins # this class will be loaded using hiera's 'puppet' backend
class ntp::data {
$ntpservers = ['1.pool.ntp.org', '2.pool.ntp.org']
}
puppet-3.4.3/examples/hiera/modules/data/ 0000755 0052762 0001160 00000000000 12300765640 020220 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/data/manifests/ 0000755 0052762 0001160 00000000000 12300765640 022211 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/data/manifests/common.pp 0000644 0052762 0001160 00000000210 12300765631 024033 0 ustar jenkins jenkins # sets the common (across all puppet conf) ntp servers.
class data::common {
$ntpservers = ['ntp1.example.com', 'ntp2.example.com']
}
puppet-3.4.3/examples/hiera/modules/users/ 0000755 0052762 0001160 00000000000 12300765640 020450 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/users/manifests/ 0000755 0052762 0001160 00000000000 12300765640 022441 5 ustar jenkins jenkins puppet-3.4.3/examples/hiera/modules/users/manifests/common.pp 0000644 0052762 0001160 00000000106 12300765631 024267 0 ustar jenkins jenkins # notifies
class users::common {
notify{'Adding users::common': }
}
puppet-3.4.3/examples/hiera/modules/users/manifests/dc1.pp 0000644 0052762 0001160 00000000100 12300765631 023440 0 ustar jenkins jenkins # notifies
class users::dc1 {
notify{'Adding users::dc1': }
}
puppet-3.4.3/examples/hiera/modules/users/manifests/development.pp 0000644 0052762 0001160 00000000120 12300765631 025315 0 ustar jenkins jenkins # notifies
class users::development {
notify{'Adding users::development': }
}
puppet-3.4.3/conf/ 0000755 0052762 0001160 00000000000 12300765640 013656 5 ustar jenkins jenkins puppet-3.4.3/conf/tagmail.conf 0000644 0052762 0001160 00000001226 12300765631 016144 0 ustar jenkins jenkins # tagmail.conf
# This file configures the `tagmail` report, which can be enabled by including
# tagmail in the puppet master's `reports` setting. (`reports = https, tagmail`)
# Each line in this file should consist of a comma-separated list of tags and/or
# negated tags (`!tag`), a colon, and a comma-separated list of email addresses.
# The `all` psuedo-tag will email all log events.
# See http://docs.puppetlabs.com/guides/configuring.html#tagmailconf for
# a complete description of this file.
# Example:
# all: log-archive@example.com
# webserver, !mailserver: httpadmins@example.com
# emerg, crit: james@example.com, zach@example.com, ben@example.com
puppet-3.4.3/conf/fileserver.conf 0000644 0052762 0001160 00000002666 12300765631 016705 0 ustar jenkins jenkins # fileserver.conf
# Puppet automatically serves PLUGINS and FILES FROM MODULES: anything in
# /files/ is available to authenticated nodes at
# puppet:///modules//. You do not need to edit this
# file to enable this.
# MOUNT POINTS
# If you need to serve files from a directory that is NOT in a module,
# you must create a static mount point in this file:
#
# [extra_files]
# path /etc/puppet/files
# allow *
#
# In the example above, anything in /etc/puppet/files/ would be
# available to authenticated nodes at puppet:///extra_files/.
#
# Mount points may also use three placeholders as part of their path:
#
# %H - The node's certname.
# %h - The portion of the node's certname before the first dot. (Usually the
# node's short hostname.)
# %d - The portion of the node's certname after the first dot. (Usually the
# node's domain name.)
# PERMISSIONS
# Every static mount point should have an `allow *` line; setting more
# granular permissions in this file is deprecated. Instead, you can
# control file access in auth.conf by controlling the
# /file_metadata/ and /file_content/ paths:
#
# path ~ ^/file_(metadata|content)/extra_files/
# auth yes
# allow /^(.+)\.example\.com$/
# allow_ip 192.168.100.0/24
#
# If added to auth.conf BEFORE the "path /file" rule, the rule above
# will add stricter restrictions to the extra_files mount point.
puppet-3.4.3/conf/auth.conf 0000644 0052762 0001160 00000010045 12300765631 015466 0 ustar jenkins jenkins # This is the default auth.conf file, which implements the default rules
# used by the puppet master. (That is, the rules below will still apply
# even if this file is deleted.)
#
# The ACLs are evaluated in top-down order. More specific stanzas should
# be towards the top of the file and more general ones at the bottom;
# otherwise, the general rules may "steal" requests that should be
# governed by the specific rules.
#
# See http://docs.puppetlabs.com/guides/rest_auth_conf.html for a more complete
# description of auth.conf's behavior.
#
# Supported syntax:
# Each stanza in auth.conf starts with a path to match, followed
# by optional modifiers, and finally, a series of allow or deny
# directives.
#
# Example Stanza
# ---------------------------------
# path /path/to/resource # simple prefix match
# # path ~ regex # alternately, regex match
# [environment envlist]
# [method methodlist]
# [auth[enthicated] {yes|no|on|off|any}]
# allow [host|backreference|*|regex]
# deny [host|backreference|*|regex]
# allow_ip [ip|cidr|ip_wildcard|*]
# deny_ip [ip|cidr|ip_wildcard|*]
#
# The path match can either be a simple prefix match or a regular
# expression. `path /file` would match both `/file_metadata` and
# `/file_content`. Regex matches allow the use of backreferences
# in the allow/deny directives.
#
# The regex syntax is the same as for Ruby regex, and captures backreferences
# for use in the `allow` and `deny` lines of that stanza
#
# Examples:
#
# path ~ ^/path/to/resource # Equivalent to `path /path/to/resource`.
# allow * # Allow all authenticated nodes (since auth
# # defaults to `yes`).
#
# path ~ ^/catalog/([^/]+)$ # Permit nodes to access their own catalog (by
# allow $1 # certname), but not any other node's catalog.
#
# path ~ ^/file_(metadata|content)/extra_files/ # Only allow certain nodes to
# auth yes # access the "extra_files"
# allow /^(.+)\.example\.com$/ # mount point; note this must
# allow_ip 192.168.100.0/24 # go ABOVE the "/file" rule,
# # since it is more specific.
#
# environment:: restrict an ACL to a comma-separated list of environments
# method:: restrict an ACL to a comma-separated list of HTTP methods
# auth:: restrict an ACL to an authenticated or unauthenticated request
# the default when unspecified is to restrict the ACL to authenticated requests
# (ie exactly as if auth yes was present).
#
### Authenticated ACLs - these rules apply only when the client
### has a valid certificate and is thus authenticated
# allow nodes to retrieve their own catalog
path ~ ^/catalog/([^/]+)$
method find
allow $1
# allow nodes to retrieve their own node definition
path ~ ^/node/([^/]+)$
method find
allow $1
# allow all nodes to access the certificates services
path /certificate_revocation_list/ca
method find
allow *
# allow all nodes to store their own reports
path ~ ^/report/([^/]+)$
method save
allow $1
# Allow all nodes to access all file services; this is necessary for
# pluginsync, file serving from modules, and file serving from custom
# mount points (see fileserver.conf). Note that the `/file` prefix matches
# requests to both the file_metadata and file_content paths. See "Examples"
# above if you need more granular access control for custom mount points.
path /file
allow *
### Unauthenticated ACLs, for clients without valid certificates; authenticated
### clients can also access these paths, though they rarely need to.
# allow access to the CA certificate; unauthenticated nodes need this
# in order to validate the puppet master's certificate
path /certificate/ca
auth any
method find
allow *
# allow nodes to retrieve the certificate they requested earlier
path /certificate/
auth any
method find
allow *
# allow nodes to request a new certificate
path /certificate_request
auth any
method find, save
allow *
# deny everything else; this ACL is not strictly necessary, but
# illustrates the default policy.
path /
auth any
puppet-3.4.3/tasks/ 0000755 0052762 0001160 00000000000 12300765640 014056 5 ustar jenkins jenkins puppet-3.4.3/tasks/parser.rake 0000644 0052762 0001160 00000000172 12300765632 016217 0 ustar jenkins jenkins
desc "Generate the parser"
task :gen_parser do
%x{racc -olib/puppet/parser/parser.rb lib/puppet/parser/grammar.ra}
end
puppet-3.4.3/tasks/ci.rake 0000644 0052762 0001160 00000001461 12300765632 015320 0 ustar jenkins jenkins require 'yaml'
require 'time'
namespace "ci" do
task :spec do
ENV["LOG_SPEC_ORDER"] = "true"
sh %{rspec -r yarjuf -f JUnit -o result.xml -fp spec}
end
desc "Tar up the acceptance/ directory so that package test runs have tests to run against."
task :acceptance_artifacts => :tag_creator do
Dir.chdir("acceptance") do
rm_f "acceptance-artifacts.tar.gz"
sh "tar -czv --exclude .bundle -f acceptance-artifacts.tar.gz *"
end
end
task :tag_creator do
Dir.chdir("acceptance") do
File.open('creator.txt', 'w') do |fh|
YAML.dump({
'creator_id' => ENV['CREATOR'] || ENV['BUILD_URL'] || 'unknown',
'created_on' => Time.now.iso8601,
'commit' => (`git log -1 --oneline` rescue "unknown: #{$!}")
}, fh)
end
end
end
end
puppet-3.4.3/tasks/manpages.rake 0000644 0052762 0001160 00000006454 12300765632 016527 0 ustar jenkins jenkins # require 'fileutils'
desc "Build Puppet manpages"
task :gen_manpages do
require 'puppet/face'
require 'fileutils'
# TODO: this line is unfortunate. In an ideal world, faces would serve
# as a clear, well-defined entry-point into the code and could be
# responsible for state management all on their own; this really should
# not be necessary. When we can, we should get rid of it.
# --cprice 2012-05-16
Puppet.initialize_settings()
helpface = Puppet::Face[:help, '0.0.1']
manface = Puppet::Face[:man, '0.0.1']
# TODO: This line is terrible. The reason we need this here is because we
# handle state initialization differently when we run via command line
# (application.rb) than we do when we try to use Faces as library code.
# This is bad, we need to come up with an official stance on what our
# API is and what the entry points, so that we can make sure that
# state initialization is consistent. See:
# http://projects.puppetlabs.com/issues/14441
Puppet::Util::Instrumentation.init()
sbins = Dir.glob(%w{sbin/*})
bins = Dir.glob(%w{bin/*})
non_face_applications = helpface.legacy_applications
faces = Puppet::Face.faces
ronn_args = '--manual="Puppet manual" --organization="Puppet Labs, LLC" -r'
# Locate ronn
ronn = %x{which ronn}.chomp
unless File.executable?(ronn) then fail("Ronn does not appear to be installed.") end
# def write_manpage(text, filename)
# IO.popen("#{ronn} #{ronn_args} -r > #{filename}") do |fh| fh.write text end
# end
# Create puppet.conf.5 man page
# IO.popen("#{ronn} #{ronn_args} > ./man/man5/puppet.conf.5", 'w') do |fh|
# fh.write %x{RUBYLIB=./lib:$RUBYLIB bin/puppetdoc --reference configuration}
# end
%x{RUBYLIB=./lib:$RUBYLIB bin/puppet doc --reference configuration > ./man/man5/puppetconf.5.ronn}
%x{#{ronn} #{ronn_args} ./man/man5/puppetconf.5.ronn}
FileUtils.mv("./man/man5/puppetconf.5", "./man/man5/puppet.conf.5")
FileUtils.rm("./man/man5/puppetconf.5.ronn")
# Create LEGACY binary man pages (i.e. delete me for 2.8.0)
binary = bins + sbins
binary.each do |bin|
b = bin.gsub( /^s?bin\//, "")
%x{RUBYLIB=./lib:$RUBYLIB #{bin} --help > ./man/man8/#{b}.8.ronn}
%x{#{ronn} #{ronn_args} ./man/man8/#{b}.8.ronn}
FileUtils.rm("./man/man8/#{b}.8.ronn")
end
# Create regular non-face man pages
non_face_applications.each do |app|
%x{RUBYLIB=./lib:$RUBYLIB bin/puppet #{app} --help > ./man/man8/puppet-#{app}.8.ronn}
%x{#{ronn} #{ronn_args} ./man/man8/puppet-#{app}.8.ronn}
FileUtils.rm("./man/man8/puppet-#{app}.8.ronn")
end
# Create face man pages
faces.each do |face|
File.open("./man/man8/puppet-#{face}.8.ronn", 'w') do |fh|
fh.write manface.man("#{face}")
end
%x{#{ronn} #{ronn_args} ./man/man8/puppet-#{face}.8.ronn}
FileUtils.rm("./man/man8/puppet-#{face}.8.ronn")
end
# Vile hack: create puppet resource man page
# Currently, the useless resource face wins against puppet resource in puppet
# man. (And actually, it even gets removed from the list of legacy
# applications.) So we overwrite it with the correct man page at the end.
%x{RUBYLIB=./lib:$RUBYLIB bin/puppet resource --help > ./man/man8/puppet-resource.8.ronn}
%x{#{ronn} #{ronn_args} ./man/man8/puppet-resource.8.ronn}
FileUtils.rm("./man/man8/puppet-resource.8.ronn")
end
puppet-3.4.3/tasks/benchmark.rake 0000644 0052762 0001160 00000005471 12300765632 016664 0 ustar jenkins jenkins require 'benchmark'
require 'tmpdir'
require 'csv'
namespace :benchmark do
def generate_scenario_tasks(location, name)
desc File.read(File.join(location, 'description'))
task name => "#{name}:run"
namespace name do
task :setup do
ENV['ITERATIONS'] ||= '10'
ENV['SIZE'] ||= '100'
ENV['TARGET'] ||= Dir.mktmpdir(name)
ENV['TARGET'] = File.expand_path(ENV['TARGET'])
mkdir_p(ENV['TARGET'])
require File.expand_path(File.join(location, 'benchmarker.rb'))
@benchmark = Benchmarker.new(ENV['TARGET'], ENV['SIZE'].to_i)
end
desc "Generate the #{name} scenario."
task :generate => :setup do
@benchmark.generate
@benchmark.setup
end
desc "Run the #{name} scenario."
task :run => :generate do
format = if RUBY_VERSION =~ /^1\.8/
Benchmark::FMTSTR
else
Benchmark::FORMAT
end
report = []
Benchmark.benchmark(Benchmark::CAPTION, 10, format, "> total:", "> avg:") do |b|
times = []
ENV['ITERATIONS'].to_i.times do |i|
start_time = Time.now.to_i
times << b.report("Run #{i + 1}") do
@benchmark.run
end
report << [to_millis(start_time), to_millis(times.last.real), 200, true, name]
end
sum = times.inject(Benchmark::Tms.new, &:+)
[sum, sum / times.length]
end
write_csv("#{name}.samples",
%w{timestamp elapsed responsecode success name},
report)
end
desc "Profile a single run of the #{name} scenario."
task :profile => :generate do
require 'ruby-prof'
result = RubyProf.profile do
@benchmark.run
end
printer = RubyProf::CallTreePrinter.new(result)
File.open(File.join("callgrind.#{name}.#{Time.now.to_i}.trace"), "w") do |f|
printer.print(f)
end
end
def to_millis(seconds)
(seconds * 1000).round
end
def write_csv(file, header, data)
CSV.open(file, 'w') do |csv|
csv << header
data.each do |line|
csv << line
end
end
end
end
end
scenarios = []
Dir.glob('benchmarks/*') do |location|
name = File.basename(location)
scenarios << name
generate_scenario_tasks(location, File.basename(location))
end
namespace :all do
desc "Profile all of the scenarios. (#{scenarios.join(', ')})"
task :profile do
scenarios.each do |name|
sh "rake benchmark:#{name}:profile"
end
end
desc "Run all of the scenarios. (#{scenarios.join(', ')})"
task :run do
scenarios.each do |name|
sh "rake benchmark:#{name}:run"
end
end
end
end
puppet-3.4.3/LICENSE 0000644 0052762 0001160 00000001276 12300765631 013744 0 ustar jenkins jenkins Puppet - Automating Configuration Management.
Copyright (C) 2005-2012 Puppet Labs Inc
Puppet Labs can be contacted at: info@puppetlabs.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
puppet-3.4.3/lib/ 0000755 0052762 0001160 00000000000 12300765640 013477 5 ustar jenkins jenkins puppet-3.4.3/lib/puppet/ 0000755 0052762 0001160 00000000000 12300765640 015014 5 ustar jenkins jenkins puppet-3.4.3/lib/puppet/provider.rb 0000644 0052762 0001160 00000061647 12300765631 017211 0 ustar jenkins jenkins # A Provider is an implementation of the actions that manage resources (of some type) on a system.
# This class is the base class for all implementation of a Puppet Provider.
#
# Concepts:
#--
# * **Confinement** - confinement restricts providers to only be applicable under certain conditions.
# It is possible to confine a provider several different ways:
# * the included {#confine} method which provides filtering on fact, feature, existence of files, or a free form
# predicate.
# * the {commands} method that filters on the availability of given system commands.
# * **Property hash** - the important instance variable `@property_hash` contains all current state values
# for properties (it is lazily built). It is important that these values are managed appropriately in the
# methods {instances}, {prefetch}, and in methods that alters the current state (those that change the
# lifecycle (creates, destroys), or alters some value reflected backed by a property).
# * **Flush** - is a hook that is called once per resource when everything has been applied. The intent is
# that an implementation may defer modification of the current state typically done in property setters
# and instead record information that allows flush to perform the changes more efficiently.
# * **Execution Methods** - The execution methods provides access to execution of arbitrary commands.
# As a convenience execution methods are available on both the instance and the class of a provider since a
# lot of provider logic switch between these contexts fairly freely.
# * **System Entity/Resource** - this documentation uses the term "system entity" for system resources to make
# it clear if talking about a resource on the system being managed (e.g. a file in the file system)
# or about a description of such a resource (e.g. a Puppet Resource).
# * **Resource Type** - this is an instance of Type that describes a classification of instances of Resource (e.g.
# the `File` resource type describes all instances of `file` resources).
# (The term is used to contrast with "type" in general, and specifically to contrast with the implementation
# class of Resource or a specific Type).
#
# @note An instance of a Provider is associated with one resource.
#
# @note Class level methods are only called once to configure the provider (when the type is created), and not
# for each resource the provider is operating on.
# The instance methods are however called for each resource.
#
# @api public
#
class Puppet::Provider
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::Warnings
extend Puppet::Util::Warnings
require 'puppet/confiner'
require 'puppet/provider/command'
extend Puppet::Confiner
Puppet::Util.logmethods(self, true)
class << self
# Include the util module so we have access to things like 'which'
include Puppet::Util, Puppet::Util::Docs
include Puppet::Util::Logging
# @return [String] The name of the provider
attr_accessor :name
#
# @todo Original = _"The source parameter exists so that providers using the same
# source can specify this, so reading doesn't attempt to read the
# same package multiple times."_ This seems to be a package type specific attribute. Is this really
# used?
#
# @return [???] The source is WHAT?
attr_writer :source
# @todo Original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_
# Is this really needed? The comment about backwards compatibility was made in 2007.
#
# @return [???] A model kept for backwards compatibility.
# @api private
# @deprecated This attribute is available for backwards compatibility reasons.
attr_reader :model
# @todo What is this type? A reference to a Puppet::Type ?
# @return [Puppet::Type] the resource type (that this provider is ... WHAT?)
#
attr_accessor :resource_type
# @!attribute [r] doc
# The (full) documentation for this provider class. The documentation for the provider class itself
# should be set with the DSL method {desc=}. Setting the documentation with with {doc=} has the same effect
# as setting it with {desc=} (only the class documentation part is set). In essence this means that
# there is no getter for the class documentation part (since the getter returns the full
# documentation when there are additional contributors).
#
# @return [String] Returns the full documentation for the provider.
# @see Puppet::Utils::Docs
# @comment This is puzzling ... a write only doc attribute??? The generated setter never seems to be
# used, instead the instance variable @doc is set in the `desc` method. This seems wrong. It is instead
# documented as a read only attribute (to get the full documentation). Also see doc below for
# desc.
# @!attribute [w] desc
# Sets the documentation of this provider class. (The full documentation is read via the
# {doc} attribute).
#
# @dsl type
#
attr_writer :doc
end
# @todo original = _"LAK 2007-05-09: Keep the model stuff around for backward compatibility"_, why is it
# both here (instance) and at class level? Is this a different model?
# @return [???] model is WHAT?
attr_reader :model
# @return [???] This resource is what? Is an instance of a provider attached to one particular Puppet::Resource?
#
attr_accessor :resource
# Convenience methods - see class method with the same name.
# @see execute
# @return (see execute)
def execute(*args)
Puppet::Util::Execution.execute(*args)
end
# (see Puppet::Util::Execution.execute)
def self.execute(*args)
Puppet::Util::Execution.execute(*args)
end
# Convenience methods - see class method with the same name.
# @see execpipe
# @return (see execpipe)
def execpipe(*args, &block)
Puppet::Util::Execution.execpipe(*args, &block)
end
# (see Puppet::Util::Execution.execpipe)
def self.execpipe(*args, &block)
Puppet::Util::Execution.execpipe(*args, &block)
end
# Convenience methods - see class method with the same name.
# @see execfail
# @return (see execfail)
def execfail(*args)
Puppet::Util::Execution.execfail(*args)
end
# (see Puppet::Util::Execution.execfail)
def self.execfail(*args)
Puppet::Util::Execution.execfail(*args)
end
# Returns the absolute path to the executable for the command referenced by the given name.
# @raise [Puppet::DevError] if the name does not reference an existing command.
# @return [String] the absolute path to the found executable for the command
# @see which
# @api public
def self.command(name)
name = name.intern
if defined?(@commands) and command = @commands[name]
# nothing
elsif superclass.respond_to? :command and command = superclass.command(name)
# nothing
else
raise Puppet::DevError, "No command #{name} defined for provider #{self.name}"
end
which(command)
end
# Confines this provider to be suitable only on hosts where the given commands are present.
# Also see {Puppet::Confiner#confine} for other types of confinement of a provider by use of other types of
# predicates.
#
# @note It is preferred if the commands are not entered with absolute paths as this allows puppet
# to search for them using the PATH variable.
#
# @param command_specs [Hash{String => String}] Map of name to command that the provider will
# be executing on the system. Each command is specified with a name and the path of the executable.
# @return [void]
# @see optional_commands
# @api public
#
def self.commands(command_specs)
command_specs.each do |name, path|
has_command(name, path)
end
end
# Defines optional commands.
# Since Puppet 2.7.8 this is typically not needed as evaluation of provider suitability
# is lazy (when a resource is evaluated) and the absence of commands
# that will be present after other resources have been applied no longer needs to be specified as
# optional.
# @param [Hash{String => String}] hash Named commands that the provider will
# be executing on the system. Each command is specified with a name and the path of the executable.
# (@see #has_command)
# @see commands
# @api public
def self.optional_commands(hash)
hash.each do |name, target|
has_command(name, target) do
is_optional
end
end
end
# Creates a convenience method for invocation of a command.
#
# This generates a Provider method that allows easy execution of the command. The generated
# method may take arguments that will be passed through to the executable as the command line arguments
# when it is invoked.
#
# @example Use it like this:
# has_command(:echo, "/bin/echo")
# def some_method
# echo("arg 1", "arg 2")
# end
# @comment the . . . below is intentional to avoid the three dots to become an illegible ellipsis char.
# @example . . . or like this
# has_command(:echo, "/bin/echo") do
# is_optional
# environment :HOME => "/var/tmp", :PWD => "/tmp"
# end
#
# @param name [Symbol] The name of the command (will become the name of the generated method that executes the command)
# @param path [String] The path to the executable for the command
# @yield [ ] A block that configures the command (see {Puppet::Provider::Command})
# @comment a yield [ ] produces {|| ...} in the signature, do not remove the space.
# @note the name ´has_command´ looks odd in an API context, but makes more sense when seen in the internal
# DSL context where a Provider is declaratively defined.
# @api public
#
def self.has_command(name, path, &block)
name = name.intern
command = CommandDefiner.define(name, path, self, &block)
@commands[name] = command.executable
# Now define the class and instance methods.
create_class_and_instance_method(name) do |*args|
return command.execute(*args)
end
end
# Internal helper class when creating commands - undocumented.
# @api private
class CommandDefiner
private_class_method :new
def self.define(name, path, confiner, &block)
definer = new(name, path, confiner)
definer.instance_eval(&block) if block
definer.command
end
def initialize(name, path, confiner)
@name = name
@path = path
@optional = false
@confiner = confiner
@custom_environment = {}
end
def is_optional
@optional = true
end
def environment(env)
@custom_environment = @custom_environment.merge(env)
end
def command
if not @optional
@confiner.confine :exists => @path, :for_binary => true
end
Puppet::Provider::Command.new(@name, @path, Puppet::Util, Puppet::Util::Execution, { :failonfail => true, :combine => true, :custom_environment => @custom_environment })
end
end
# @return [Boolean] Return whether the given feature has been declared or not.
def self.declared_feature?(name)
defined?(@declared_features) and @declared_features.include?(name)
end
# @return [Boolean] Returns whether this implementation satisfies all of the default requirements or not.
# Returns false If defaults are empty.
# @see Provider.defaultfor
#
def self.default?
return false if @defaults.empty?
if @defaults.find do |fact, values|
values = [values] unless values.is_a? Array
if fval = Facter.value(fact).to_s and fval != ""
fval = fval.to_s.downcase.intern
else
return false
end
# If any of the values match, we're a default.
if values.find do |value| fval == value.to_s.downcase.intern end
false
else
true
end
end
return false
else
return true
end
end
# Sets a facts filter that determine which of several suitable providers should be picked by default.
# This selection only kicks in if there is more than one suitable provider.
# To filter on multiple facts the given hash may contain more than one fact name/value entry.
# The filter picks the provider if all the fact/value entries match the current set of facts. (In case
# there are still more than one provider after this filtering, the first found is picked).
# @param hash [Hash<{String => Object}>] hash of fact name to fact value.
# @return [void]
#
def self.defaultfor(hash)
hash.each do |d,v|
@defaults[d] = v
end
end
# @return [Integer] Returns a numeric specificity for this provider based on how many requirements it has
# and number of _ancestors_. The higher the number the more specific the provider.
# The number of requirements is based on the number of defaults set up with {Provider.defaultfor}.
#
# The _ancestors_ is the Ruby Module::ancestors method and the number of classes returned is used
# to boost the score. The intent is that if two providers are equal, but one is more "derived" than the other
# (i.e. includes more classes), it should win because it is more specific).
# @note Because of how this value is
# calculated there could be surprising side effects if a provider included an excessive amount of classes.
#
def self.specificity
# This strange piece of logic attempts to figure out how many parent providers there
# are to increase the score. What is will actually do is count all classes that Ruby Module::ancestors
# returns (which can be other classes than those the parent chain) - in a way, an odd measure of the
# complexity of a provider).
(@defaults.length * 100) + ancestors.select { |a| a.is_a? Class }.length
end
# Initializes defaults and commands (i.e. clears them).
# @return [void]
def self.initvars
@defaults = {}
@commands = {}
end
# Returns a list of system resources (entities) this provider may/can manage.
# This is a query mechanism that lists entities that the provider may manage on a given system. It is
# is directly used in query services, but is also the foundation for other services; prefetching, and
# purging.
#
# As an example, a package provider lists all installed packages. (In contrast, the File provider does
# not list all files on the file-system as that would make execution incredibly slow). An implementation
# of this method should be made if it is possible to quickly (with a single system call) provide all
# instances.
#
# An implementation of this method should only cache the values of properties
# if they are discovered as part of the process for finding existing resources.
# Resource properties that require additional commands (than those used to determine existence/identity)
# should be implemented in their respective getter method. (This is important from a performance perspective;
# it may be expensive to compute, as well as wasteful as all discovered resources may perhaps not be managed).
#
# An implementation may return an empty list (naturally with the effect that it is not possible to query
# for manageable entities).
#
# By implementing this method, it is possible to use the `resources´ resource type to specify purging
# of all non managed entities.
#
# @note The returned instances are instance of some subclass of Provider, not resources.
# @return [Array] a list of providers referencing the system entities
# @abstract this method must be implemented by a subclass and this super method should never be called as it raises an exception.
# @raise [Puppet::DevError] Error indicating that the method should have been implemented by subclass.
# @see prefetch
def self.instances
raise Puppet::DevError, "Provider #{self.name} has not defined the 'instances' class method"
end
# Creates the methods for a given command.
# @api private
# @deprecated Use {commands}, {optional_commands}, or {has_command} instead. This was not meant to be part of a public API
def self.make_command_methods(name)
Puppet.deprecation_warning "Provider.make_command_methods is deprecated; use Provider.commands or Provider.optional_commands instead for creating command methods"
# Now define a method for that command
unless singleton_class.method_defined?(name)
meta_def(name) do |*args|
# This might throw an ExecutionFailure, but the system above
# will catch it, if so.
command = Puppet::Provider::Command.new(name, command(name), Puppet::Util, Puppet::Util::Execution)
return command.execute(*args)
end
# And then define an instance method that just calls the class method.
# We need both, so both instances and classes can easily run the commands.
unless method_defined?(name)
define_method(name) do |*args|
self.class.send(name, *args)
end
end
end
end
# Creates getter- and setter- methods for each property supported by the resource type.
# Call this method to generate simple accessors for all properties supported by the
# resource type. These simple accessors lookup and sets values in the property hash.
# The generated methods may be overridden by more advanced implementations if something
# else than a straight forward getter/setter pair of methods is required.
# (i.e. define such overriding methods after this method has been called)
#
# An implementor of a provider that makes use of `prefetch` and `flush` can use this method since it uses
# the internal `@property_hash` variable to store values. An implementation would then update the system
# state on a call to `flush` based on the current values in the `@property_hash`.
#
# @return [void]
#
def self.mk_resource_methods
[resource_type.validproperties, resource_type.parameters].flatten.each do |attr|
attr = attr.intern
next if attr == :name
define_method(attr) do
@property_hash[attr] || :absent
end
define_method(attr.to_s + "=") do |val|
@property_hash[attr] = val
end
end
end
self.initvars
# This method is used to generate a method for a command.
# @return [void]
# @api private
#
def self.create_class_and_instance_method(name, &block)
unless singleton_class.method_defined?(name)
meta_def(name, &block)
end
unless method_defined?(name)
define_method(name) do |*args|
self.class.send(name, *args)
end
end
end
private_class_method :create_class_and_instance_method
# @return [String] Returns the data source, which is the provider name if no other source has been set.
# @todo Unclear what "the source" is used for?
def self.source
@source ||= self.name
end
# Returns true if the given attribute/parameter is supported by the provider.
# The check is made that the parameter is a valid parameter for the resource type, and then
# if all its required features (if any) are supported by the provider.
#
# @param param [Class, Puppet::Parameter] the parameter class, or a parameter instance
# @return [Boolean] Returns whether this provider supports the given parameter or not.
# @raise [Puppet::DevError] if the given parameter is not valid for the resource type
#
def self.supports_parameter?(param)
if param.is_a?(Class)
klass = param
else
unless klass = resource_type.attrclass(param)
raise Puppet::DevError, "'#{param}' is not a valid parameter for #{resource_type.name}"
end
end
return true unless features = klass.required_features
!!satisfies?(*features)
end
# def self.to_s
# unless defined?(@str)
# if self.resource_type
# @str = "#{resource_type.name} provider #{self.name}"
# else
# @str = "unattached provider #{self.name}"
# end
# end
# @str
# end
dochook(:defaults) do
if @defaults.length > 0
return "Default for " + @defaults.collect do |f, v|
"`#{f}` == `#{[v].flatten.join(', ')}`"
end.sort.join(" and ") + "."
end
end
dochook(:commands) do
if @commands.length > 0
return "Required binaries: " + @commands.collect do |n, c|
"`#{c}`"
end.sort.join(", ") + "."
end
end
dochook(:features) do
if features.length > 0
return "Supported features: " + features.collect do |f|
"`#{f}`"
end.sort.join(", ") + "."
end
end
# Clears this provider instance to allow GC to clean up.
def clear
@resource = nil
@model = nil
end
# (see command)
def command(name)
self.class.command(name)
end
# Returns the value of a parameter value, or `:absent` if it is not defined.
# @param param [Puppet::Parameter] the parameter to obtain the value of
# @return [Object] the value of the parameter or `:absent` if not defined.
#
def get(param)
@property_hash[param.intern] || :absent
end
# Creates a new provider that is optionally initialized from a resource or a hash of properties.
# If no argument is specified, a new non specific provider is initialized. If a resource is given
# it is remembered for further operations. If a hash is used it becomes the internal `@property_hash`
# structure of the provider - this hash holds the current state property values of system entities
# as they are being discovered by querying or other operations (typically getters).
#
# @todo The use of a hash as a parameter needs a better exaplanation; why is this done? What is the intent?
# @param resource [Puppet::Resource, Hash] optional resource or hash
#
def initialize(resource = nil)
if resource.is_a?(Hash)
# We don't use a duplicate here, because some providers (ParsedFile, at least)
# use the hash here for later events.
@property_hash = resource
elsif resource
@resource = resource
# LAK 2007-05-09: Keep the model stuff around for backward compatibility
@model = resource
@property_hash = {}
else
@property_hash = {}
end
end
# Returns the name of the resource this provider is operating on.
# @return [String] the name of the resource instance (e.g. the file path of a File).
# @raise [Puppet::DevError] if no resource is set, or no name defined.
#
def name
if n = @property_hash[:name]
return n
elsif self.resource
resource.name
else
raise Puppet::DevError, "No resource and no name in property hash in #{self.class.name} instance"
end
end
# Sets the given parameters values as the current values for those parameters.
# Other parameters are unchanged.
# @param [Array] params the parameters with values that should be set
# @return [void]
#
def set(params)
params.each do |param, value|
@property_hash[param.intern] = value
end
end
# @return [String] Returns a human readable string with information about the resource and the provider.
def to_s
"#{@resource}(provider=#{self.class.name})"
end
# Makes providers comparable.
include Comparable
# Compares this provider against another provider.
# Comparison is only possible with another provider (no other class).
# The ordering is based on the class name of the two providers.
#
# @return [-1,0,+1, nil] A comparison result -1, 0, +1 if this is before other, equal or after other. Returns
# nil oif not comparable to other.
# @see Comparable
def <=>(other)
# We can only have ordering against other providers.
return nil unless other.is_a? Puppet::Provider
# Otherwise, order by the providers class name.
return self.class.name <=> other.class.name
end
# @comment Document prefetch here as it does not exist anywhere else (called from transaction if implemented)
# @!method self.prefetch(resource_hash)
# @abstract A subclass may implement this - it is not implemented in the Provider class
# This method may be implemented by a provider in order to pre-fetch resource properties.
# If implemented it should set the provider instance of the managed resources to a provider with the
# fetched state (i.e. what is returned from the {instances} method).
# @param resources_hash [Hash<{String => Puppet::Resource}>] map from name to resource of resources to prefetch
# @return [void]
# @api public
# @comment Document post_resource_eval here as it does not exist anywhere else (called from transaction if implemented)
# @!method self.post_resource_eval()
# @since 3.4.0
# @api public
# @abstract A subclass may implement this - it is not implemented in the Provider class
# This method may be implemented by a provider in order to perform any
# cleanup actions needed. It will be called at the end of the transaction if
# any resources in the catalog make use of the provider, regardless of
# whether the resources are changed or not and even if resource failures occur.
# @return [void]
# @comment Document flush here as it does not exist anywhere (called from transaction if implemented)
# @!method flush()
# @abstract A subclass may implement this - it is not implemented in the Provider class
# This method may be implemented by a provider in order to flush properties that has not been individually
# applied to the managed entity's current state.
# @return [void]
# @api public
end
puppet-3.4.3/lib/puppet/vendor.rb 0000644 0052762 0001160 00000003555 12300765631 016646 0 ustar jenkins jenkins module Puppet
# Simple module to manage vendored code.
#
# To vendor a library:
#
# * Download its whole git repo or untar into `lib/puppet/vendor/`
# * Create a lib/puppetload_libraryname.rb file to add its libdir into the $:.
# (Look at existing load_xxx files, they should all follow the same pattern).
# * To load the vendored lib upfront, add a `require ''`line to
# `vendor/require_vendored.rb`.
# * To load the vendored lib on demand, add a comment to `vendor/require_vendored.rb`
# to make it clear it should not be loaded upfront.
#
# At runtime, the #load_vendored method should be called. It will ensure
# all vendored libraries are added to the global `$:` path, and
# will then call execute the up-front loading specified in `vendor/require_vendored.rb`.
#
# The intention is to not change vendored libraries and to eventually
# make adding them in optional so that distros can simply adjust their
# packaging to exclude this directory and the various load_xxx.rb scripts
# if they wish to install these gems as native packages.
#
class Vendor
class << self
# @api private
def vendor_dir
File.join([File.dirname(File.expand_path(__FILE__)), "vendor"])
end
# @api private
def load_entry(entry)
Puppet.debug("Loading vendored #{$1}")
load "#{vendor_dir}/#{entry}"
end
# @api private
def require_libs
require 'puppet/vendor/require_vendored'
end
# Configures the path for all vendored libraries and loads required libraries.
# (This is the entry point for loading vendored libraries).
#
def load_vendored
Dir.entries(vendor_dir).each do |entry|
if entry.match(/load_(\w+?)\.rb$/)
load_entry entry
end
end
require_libs
end
end
end
end
puppet-3.4.3/lib/puppet/reports/ 0000755 0052762 0001160 00000000000 12300765640 016512 5 ustar jenkins jenkins puppet-3.4.3/lib/puppet/reports/http.rb 0000644 0052762 0001160 00000001576 12300765631 020027 0 ustar jenkins jenkins require 'puppet'
require 'puppet/network/http_pool'
require 'uri'
Puppet::Reports.register_report(:http) do
desc <<-DESC
Send reports via HTTP or HTTPS. This report processor submits reports as
POST requests to the address in the `reporturl` setting. The body of each POST
request is the YAML dump of a Puppet::Transaction::Report object, and the
Content-Type is set as `application/x-yaml`.
DESC
def process
url = URI.parse(Puppet[:reporturl])
body = self.to_yaml
headers = { "Content-Type" => "application/x-yaml" }
use_ssl = url.scheme == 'https'
conn = Puppet::Network::HttpPool.http_instance(url.host, url.port, use_ssl)
response = conn.post(url.path, body, headers)
unless response.kind_of?(Net::HTTPSuccess)
Puppet.err "Unable to submit report to #{Puppet[:reporturl].to_s} [#{response.code}] #{response.msg}"
end
end
end
puppet-3.4.3/lib/puppet/reports/tagmail.rb 0000644 0052762 0001160 00000013372 12300765631 020463 0 ustar jenkins jenkins require 'puppet'
require 'pp'
require 'net/smtp'
require 'time'
Puppet::Reports.register_report(:tagmail) do
desc "This report sends specific log messages to specific email addresses
based on the tags in the log messages.
See the [documentation on tags](http://projects.puppetlabs.com/projects/puppet/wiki/Using_Tags) for more information.
To use this report, you must create a `tagmail.conf` file in the location
specified by the `tagmap` setting. This is a simple file that maps tags to
email addresses: Any log messages in the report that match the specified
tags will be sent to the specified email addresses.
Lines in the `tagmail.conf` file consist of a comma-separated list
of tags, a colon, and a comma-separated list of email addresses.
Tags can be !negated with a leading exclamation mark, which will
subtract any messages with that tag from the set of events handled
by that line.
Puppet's log levels (`debug`, `info`, `notice`, `warning`, `err`,
`alert`, `emerg`, `crit`, and `verbose`) can also be used as tags,
and there is an `all` tag that will always match all log messages.
An example `tagmail.conf`:
all: me@domain.com
webserver, !mailserver: httpadmins@domain.com
This will send all messages to `me@domain.com`, and all messages from
webservers that are not also from mailservers to `httpadmins@domain.com`.
If you are using anti-spam controls such as grey-listing on your mail
server, you should whitelist the sending email address (controlled by
`reportfrom` configuration option) to ensure your email is not discarded as spam.
"
# Find all matching messages.
def match(taglists)
matching_logs = []
taglists.each do |emails, pos, neg|
# First find all of the messages matched by our positive tags
messages = nil
if pos.include?("all")
messages = self.logs
else
# Find all of the messages that are tagged with any of our
# tags.
messages = self.logs.find_all do |log|
pos.detect { |tag| log.tagged?(tag) }
end
end
# Now go through and remove any messages that match our negative tags
messages = messages.reject do |log|
true if neg.detect do |tag| log.tagged?(tag) end
end
if messages.empty?
Puppet.info "No messages to report to #{emails.join(",")}"
next
else
matching_logs << [emails, messages.collect { |m| m.to_report }.join("\n")]
end
end
matching_logs
end
# Load the config file
def parse(text)
taglists = []
text.split("\n").each do |line|
taglist = emails = nil
case line.chomp
when /^\s*#/; next
when /^\s*$/; next
when /^\s*(.+)\s*:\s*(.+)\s*$/
taglist = $1
emails = $2.sub(/#.*$/,'')
else
raise ArgumentError, "Invalid tagmail config file"
end
pos = []
neg = []
taglist.sub(/\s+$/,'').split(/\s*,\s*/).each do |tag|
unless tag =~ /^!?[-\w\.]+$/
raise ArgumentError, "Invalid tag #{tag.inspect}"
end
case tag
when /^\w+/; pos << tag
when /^!\w+/; neg << tag.sub("!", '')
else
raise Puppet::Error, "Invalid tag '#{tag}'"
end
end
# Now split the emails
emails = emails.sub(/\s+$/,'').split(/\s*,\s*/)
taglists << [emails, pos, neg]
end
taglists
end
# Process the report. This just calls the other associated messages.
def process
unless Puppet::FileSystem::File.exist?(Puppet[:tagmap])
Puppet.notice "Cannot send tagmail report; no tagmap file #{Puppet[:tagmap]}"
return
end
metrics = raw_summary['resources'] || {} rescue {}
if metrics['out_of_sync'] == 0 && metrics['changed'] == 0
Puppet.notice "Not sending tagmail report; no changes"
return
end
taglists = parse(File.read(Puppet[:tagmap]))
# Now find any appropriately tagged messages.
reports = match(taglists)
send(reports) unless reports.empty?
end
# Send the email reports.
def send(reports)
pid = Puppet::Util.safe_posix_fork do
if Puppet[:smtpserver] != "none"
begin
Net::SMTP.start(Puppet[:smtpserver], Puppet[:smtpport], Puppet[:smtphelo]) do |smtp|
reports.each do |emails, messages|
smtp.open_message_stream(Puppet[:reportfrom], *emails) do |p|
p.puts "From: #{Puppet[:reportfrom]}"
p.puts "Subject: Puppet Report for #{self.host}"
p.puts "To: " + emails.join(", ")
p.puts "Date: #{Time.now.rfc2822}"
p.puts
p.puts messages
end
end
end
rescue => detail
message = "Could not send report emails through smtp: #{detail}"
Puppet.log_exception(detail, message)
raise Puppet::Error, message
end
elsif Puppet[:sendmail] != ""
begin
reports.each do |emails, messages|
# We need to open a separate process for every set of email addresses
IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p|
p.puts "From: #{Puppet[:reportfrom]}"
p.puts "Subject: Puppet Report for #{self.host}"
p.puts "To: " + emails.join(", ")
p.puts
p.puts messages
end
end
rescue => detail
message = "Could not send report emails via sendmail: #{detail}"
Puppet.log_exception(detail, message)
raise Puppet::Error, message
end
else
raise Puppet::Error, "SMTP server is unset and could not find sendmail"
end
end
# Don't bother waiting for the pid to return.
Process.detach(pid)
end
end
puppet-3.4.3/lib/puppet/reports/store.rb 0000644 0052762 0001160 00000003667 12300765631 020207 0 ustar jenkins jenkins require 'puppet'
require 'fileutils'
require 'tempfile'
SEPARATOR = [Regexp.escape(File::SEPARATOR.to_s), Regexp.escape(File::ALT_SEPARATOR.to_s)].join
Puppet::Reports.register_report(:store) do
desc "Store the yaml report on disk. Each host sends its report as a YAML dump
and this just stores the file on disk, in the `reportdir` directory.
These files collect quickly -- one every half hour -- so it is a good idea
to perform some maintenance on them if you use this report (it's the only
default report)."
def process
validate_host(host)
dir = File.join(Puppet[:reportdir], host)
if ! Puppet::FileSystem::File.exist?(dir)
FileUtils.mkdir_p(dir)
FileUtils.chmod_R(0750, dir)
end
# Now store the report.
now = Time.now.gmtime
name = %w{year month day hour min}.collect do |method|
# Make sure we're at least two digits everywhere
"%02d" % now.send(method).to_s
end.join("") + ".yaml"
file = File.join(dir, name)
f = Tempfile.new(name, dir)
begin
begin
f.chmod(0640)
f.print to_yaml
ensure
f.close
end
FileUtils.mv(f.path, file)
rescue => detail
Puppet.log_exception(detail, "Could not write report for #{host} at #{file}: #{detail}")
end
# Only testing cares about the return value
file
end
# removes all reports for a given host?
def self.destroy(host)
validate_host(host)
dir = File.join(Puppet[:reportdir], host)
if Puppet::FileSystem::File.exist?(dir)
Dir.entries(dir).each do |file|
next if ['.','..'].include?(file)
file = File.join(dir, file)
Puppet::FileSystem::File.unlink(file) if File.file?(file)
end
Dir.rmdir(dir)
end
end
def validate_host(host)
if host =~ Regexp.union(/[#{SEPARATOR}]/, /\A\.\.?\Z/)
raise ArgumentError, "Invalid node name #{host.inspect}"
end
end
module_function :validate_host
end
puppet-3.4.3/lib/puppet/reports/log.rb 0000644 0052762 0001160 00000000506 12300765631 017621 0 ustar jenkins jenkins require 'puppet/reports'
Puppet::Reports.register_report(:log) do
desc "Send all received logs to the local log destinations. Usually
the log destination is syslog."
def process
self.logs.each do |log|
log.source = "//#{self.host}/#{log.source}"
Puppet::Util::Log.newmessage(log)
end
end
end
puppet-3.4.3/lib/puppet/reports/rrdgraph.rb 0000644 0052762 0001160 00000010257 12300765631 020655 0 ustar jenkins jenkins Puppet::Reports.register_report(:rrdgraph) do
desc "Graph all available data about hosts using the RRD library. You
must have the Ruby RRDtool library installed to use this report, which
you can get from
[the RubyRRDTool RubyForge page](http://rubyforge.org/projects/rubyrrdtool/).
This package may also be available as `librrd-ruby`, `ruby-rrd`, or `rrdtool-ruby` in your
distribution's package management system. The library and/or package will both
require the binary `rrdtool` package from your distribution to be installed.
This report will create, manage, and graph RRD database files for each
of the metrics generated during transactions, and it will create a
few simple html files to display the reporting host's graphs. At this
point, it will not create a common index file to display links to
all hosts.
All RRD files and graphs get created in the `rrddir` directory. If
you want to serve these publicly, you should be able to just alias that
directory in a web server.
If you really know what you're doing, you can tune the `rrdinterval`,
which defaults to the `runinterval`."
def hostdir
@hostdir ||= File.join(Puppet[:rrddir], self.host)
end
def htmlfile(type, graphs, field)
file = File.join(hostdir, "#{type}.html")
File.open(file, "w") do |of|
of.puts "#{type.capitalize} graphs for #{host}"
graphs.each do |graph|
if field == :first
name = graph.sub(/-\w+.png/, '').capitalize
else
name = graph.sub(/\w+-/, '').sub(".png", '').capitalize
end
of.puts "
"
end
of.puts ""
end
file
end
def mkhtml
images = Dir.entries(hostdir).find_all { |d| d =~ /\.png/ }
periodorder = %w{daily weekly monthly yearly}
periods = {}
types = {}
images.each do |n|
type, period = n.sub(".png", '').split("-")
periods[period] ||= []
types[type] ||= []
periods[period] << n
types[type] << n
end
files = []
# Make the period html files
periodorder.each do |period|
unless ary = periods[period]
raise Puppet::Error, "Could not find graphs for #{period}"
end
files << htmlfile(period, ary, :first)
end
# make the type html files
types.sort { |a,b| a[0] <=> b[0] }.each do |type, ary|
newary = []
periodorder.each do |period|
if graph = ary.find { |g| g.include?("-#{period}.png") }
newary << graph
else
raise "Could not find #{type}-#{period} graph"
end
end
files << htmlfile(type, newary, :second)
end
File.open(File.join(hostdir, "index.html"), "w") do |of|
of.puts "Report graphs for #{host}"
files.each do |file|
of.puts "#{File.basename(file).sub(".html",'').capitalize}
"
end
of.puts ""
end
end
def process(time = nil)
time ||= Time.now.to_i
unless File.directory?(hostdir) and FileTest.writable?(hostdir)
# Some hackishness to create the dir with all of the right modes and ownership
config = Puppet::Settings.new
config.define_settings(:reports, :hostdir => {:type => :directory, :default => hostdir, :owner => 'service', :mode => 0755, :group => 'service', :desc => "eh"})
# This creates the dir.
config.use(:reports)
end
self.metrics.each do |name, metric|
metric.basedir = hostdir
if name == "time"
timeclean(metric)
end
metric.store(time)
metric.graph
end
mkhtml unless Puppet::FileSystem::File.exist?(File.join(hostdir, "index.html"))
end
# Unfortunately, RRD does not deal well with changing lists of values,
# so we have to pick a list of values and stick with it. In this case,
# that means we record the total time, the config time, and that's about
# it. We should probably send each type's time as a separate metric.
def timeclean(metric)
metric.values = metric.values.find_all { |name, label, value| ['total', 'config_retrieval'].include?(name.to_s) }
end
end
puppet-3.4.3/lib/puppet/indirector.rb 0000644 0052762 0001160 00000004753 12300765631 017514 0 ustar jenkins jenkins # Manage indirections to termini. They are organized in terms of indirections -
# - e.g., configuration, node, file, certificate -- and each indirection has one
# or more terminus types defined. The indirection is configured via the
# +indirects+ method, which will be called by the class extending itself
# with this module.
module Puppet::Indirector
# LAK:FIXME We need to figure out how to handle documentation for the
# different indirection types.
require 'puppet/indirector/indirection'
require 'puppet/indirector/terminus'
require 'puppet/indirector/envelope'
require 'puppet/network/format_support'
def self.configure_routes(application_routes)
application_routes.each do |indirection_name, termini|
indirection_name = indirection_name.to_sym
terminus_name = termini["terminus"]
cache_name = termini["cache"]
Puppet::Indirector::Terminus.terminus_class(indirection_name, terminus_name || cache_name)
indirection = Puppet::Indirector::Indirection.instance(indirection_name)
raise "Indirection #{indirection_name} does not exist" unless indirection
indirection.terminus_class = terminus_name if terminus_name
indirection.cache_class = cache_name if cache_name
end
end
# Declare that the including class indirects its methods to
# this terminus. The terminus name must be the name of a Puppet
# default, not the value -- if it's the value, then it gets
# evaluated at parse time, which is before the user has had a chance
# to override it.
def indirects(indirection, options = {})
raise(ArgumentError, "Already handling indirection for #{@indirection.name}; cannot also handle #{indirection}") if @indirection
# populate this class with the various new methods
extend ClassMethods
include Puppet::Indirector::Envelope
include Puppet::Network::FormatSupport
# record the indirected class name for documentation purposes
options[:indirected_class] = name
# instantiate the actual Terminus for that type and this name (:ldap, w/ args :node)
# & hook the instantiated Terminus into this class (Node: @indirection = terminus)
@indirection = Puppet::Indirector::Indirection.new(self, indirection, options)
end
module ClassMethods
attr_reader :indirection
end
# Helper definition for indirections that handle filenames.
BadNameRegexp = Regexp.union(/^\.\./,
%r{[\\/]},
"\0",
/(?i)^[a-z]:/)
end
puppet-3.4.3/lib/puppet/ssl/ 0000755 0052762 0001160 00000000000 12300765640 015615 5 ustar jenkins jenkins puppet-3.4.3/lib/puppet/ssl/host.rb 0000644 0052762 0001160 00000026461 12300765631 017130 0 ustar jenkins jenkins require 'puppet/indirector'
require 'puppet/ssl'
require 'puppet/ssl/key'
require 'puppet/ssl/certificate'
require 'puppet/ssl/certificate_request'
require 'puppet/ssl/certificate_revocation_list'
require 'puppet/ssl/certificate_request_attributes'
# The class that manages all aspects of our SSL certificates --
# private keys, public keys, requests, etc.
class Puppet::SSL::Host
# Yay, ruby's strange constant lookups.
Key = Puppet::SSL::Key
CA_NAME = Puppet::SSL::CA_NAME
Certificate = Puppet::SSL::Certificate
CertificateRequest = Puppet::SSL::CertificateRequest
CertificateRevocationList = Puppet::SSL::CertificateRevocationList
extend Puppet::Indirector
indirects :certificate_status, :terminus_class => :file, :doc => < :file, :disabled_ca => nil, :file => nil, :rest => :rest}
if term = host_map[terminus]
self.indirection.terminus_class = term
else
self.indirection.reset_terminus_class
end
if cache
# This is weird; we don't actually cache our keys, we
# use what would otherwise be the cache as our normal
# terminus.
Key.indirection.terminus_class = cache
else
Key.indirection.terminus_class = terminus
end
if cache
Certificate.indirection.cache_class = cache
CertificateRequest.indirection.cache_class = cache
CertificateRevocationList.indirection.cache_class = cache
else
# Make sure we have no cache configured. puppet master
# switches the configurations around a bit, so it's important
# that we specify the configs for absolutely everything, every
# time.
Certificate.indirection.cache_class = nil
CertificateRequest.indirection.cache_class = nil
CertificateRevocationList.indirection.cache_class = nil
end
end
CA_MODES = {
# Our ca is local, so we use it as the ultimate source of information
# And we cache files locally.
:local => [:ca, :file],
# We're a remote CA client.
:remote => [:rest, :file],
# We are the CA, so we don't have read/write access to the normal certificates.
:only => [:ca],
# We have no CA, so we just look in the local file store.
:none => [:disabled_ca]
}
# Specify how we expect to interact with our certificate authority.
def self.ca_location=(mode)
modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ")
raise ArgumentError, "CA Mode can only be one of: #{modes}" unless CA_MODES.include?(mode)
@ca_location = mode
configure_indirection(*CA_MODES[@ca_location])
end
# Puppet::SSL::Host is actually indirected now so the original implementation
# has been moved into the certificate_status indirector. This method is in-use
# in `puppet cert -c `.
def self.destroy(name)
indirection.destroy(name)
end
def self.from_pson(pson)
instance = new(pson["name"])
if pson["desired_state"]
instance.desired_state = pson["desired_state"]
end
instance
end
# Puppet::SSL::Host is actually indirected now so the original implementation
# has been moved into the certificate_status indirector. This method does not
# appear to be in use in `puppet cert -l`.
def self.search(options = {})
indirection.search("*", options)
end
# Is this a ca host, meaning that all of its files go in the CA location?
def ca?
ca
end
def key
@key ||= Key.indirection.find(name)
end
# This is the private key; we can create it from scratch
# with no inputs.
def generate_key
@key = Key.new(name)
@key.generate
begin
Key.indirection.save(@key)
rescue
@key = nil
raise
end
true
end
def certificate_request
@certificate_request ||= CertificateRequest.indirection.find(name)
end
# Our certificate request requires the key but that's all.
def generate_certificate_request(options = {})
generate_key unless key
# If this CSR is for the current machine...
if name == Puppet[:certname].downcase
# ...add our configured dns_alt_names
if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != ''
options[:dns_alt_names] ||= Puppet[:dns_alt_names]
elsif Puppet::SSL::CertificateAuthority.ca? and fqdn = Facter.value(:fqdn) and domain = Facter.value(:domain)
options[:dns_alt_names] = "puppet, #{fqdn}, puppet.#{domain}"
end
end
csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes])
if csr_attributes.load
options[:csr_attributes] = csr_attributes.custom_attributes
options[:extension_requests] = csr_attributes.extension_requests
end
@certificate_request = CertificateRequest.new(name)
@certificate_request.generate(key.content, options)
begin
CertificateRequest.indirection.save(@certificate_request)
rescue
@certificate_request = nil
raise
end
true
end
def certificate
unless @certificate
generate_key unless key
# get the CA cert first, since it's required for the normal cert
# to be of any use.
return nil unless Certificate.indirection.find("ca") unless ca?
return nil unless @certificate = Certificate.indirection.find(name)
validate_certificate_with_key
end
@certificate
end
def validate_certificate_with_key
raise Puppet::Error, "No certificate to validate." unless certificate
raise Puppet::Error, "No private key with which to validate certificate with fingerprint: #{certificate.fingerprint}" unless key
unless certificate.content.check_private_key(key.content)
raise Puppet::Error, < name }
my_state = state
result[:state] = my_state
result[:desired_state] = desired_state if desired_state
thing_to_use = (my_state == 'requested') ? certificate_request : my_cert
# this is for backwards-compatibility
# we should deprecate it and transition people to using
# pson[:fingerprints][:default]
# It appears that we have no internal consumers of this api
# --jeffweiss 30 aug 2012
result[:fingerprint] = thing_to_use.fingerprint
# The above fingerprint doesn't tell us what message digest algorithm was used
# No problem, except that the default is changing between 2.7 and 3.0. Also, as
# we move to FIPS 140-2 compliance, MD5 is no longer allowed (and, gasp, will
# segfault in rubies older than 1.9.3)
# So, when we add the newer fingerprints, we're explicit about the hashing
# algorithm used.
# --jeffweiss 31 july 2012
result[:fingerprints] = {}
result[:fingerprints][:default] = thing_to_use.fingerprint
suitable_message_digest_algorithms.each do |md|
result[:fingerprints][md] = thing_to_use.fingerprint md
end
result[:dns_alt_names] = thing_to_use.subject_alt_names
result
end
def to_pson(*args)
to_data_hash.to_pson(*args)
end
# eventually we'll probably want to move this somewhere else or make it
# configurable
# --jeffweiss 29 aug 2012
def suitable_message_digest_algorithms
[:SHA1, :SHA256, :SHA512]
end
# Attempt to retrieve a cert, if we don't already have one.
def wait_for_cert(time)
begin
return if certificate
generate
return if certificate
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
Puppet.log_exception(detail, "Could not request certificate: #{detail.message}")
if time < 1
puts "Exiting; failed to retrieve certificate and waitforcert is disabled"
exit(1)
else
sleep(time)
end
retry
end
if time < 1
puts "Exiting; no certificate found and waitforcert is disabled"
exit(1)
end
while true
sleep time
begin
break if certificate
Puppet.notice "Did not receive certificate"
rescue StandardError => detail
Puppet.log_exception(detail, "Could not request certificate: #{detail.message}")
end
end
end
def state
if certificate_request
return 'requested'
end
begin
Puppet::SSL::CertificateAuthority.new.verify(name)
return 'signed'
rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError
return 'revoked'
end
end
end
require 'puppet/ssl/certificate_authority'
puppet-3.4.3/lib/puppet/ssl/configuration.rb 0000644 0052762 0001160 00000003762 12300765631 021021 0 ustar jenkins jenkins require 'puppet/ssl'
require 'openssl'
module Puppet
module SSL
# Puppet::SSL::Configuration is intended to separate out the following concerns:
# * CA certificates that authenticate peers (ca_auth_file)
# * CA certificates that build trust but do not authenticate (ca_chain_file)
# * Who clients trust as distinct from who servers trust. We should not
# assume one single self signed CA cert for everyone.
class Configuration
def initialize(localcacert, options={})
if (options[:ca_chain_file] and not options[:ca_auth_file])
raise ArgumentError, "The CA auth chain is required if the chain file is provided"
end
@localcacert = localcacert
@ca_chain_file = options[:ca_chain_file]
@ca_auth_file = options[:ca_auth_file]
end
# The ca_chain_file method is intended to return the PEM bundle of CA certs
# establishing trust but not used for peer authentication.
def ca_chain_file
@ca_chain_file || ca_auth_file
end
# The ca_auth_file method is intended to return the PEM bundle of CA certs
# used to authenticate peer connections.
def ca_auth_file
@ca_auth_file || @localcacert
end
##
# ca_auth_certificates returns an Array of OpenSSL::X509::Certificate
# instances intended to be used in the connection verify_callback. This
# method loads and parses the {#ca_auth_file} from the filesystem.
#
# @api private
#
# @return [Array]
def ca_auth_certificates
@ca_auth_certificates ||= decode_cert_bundle(read_file(ca_auth_file))
end
##
# Decode a string of concatenated certificates
#
# @return [Array]
def decode_cert_bundle(bundle_str)
re = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
pem_ary = bundle_str.scan(re)
pem_ary.map do |pem_str|
OpenSSL::X509::Certificate.new(pem_str)
end
end
private :decode_cert_bundle
# read_file makes testing easier.
def read_file(path)
File.read(path)
end
private :read_file
end
end
end
puppet-3.4.3/lib/puppet/ssl/certificate_authority.rb 0000644 0052762 0001160 00000040661 12300765631 022543 0 ustar jenkins jenkins require 'puppet/ssl/host'
require 'puppet/ssl/certificate_request'
require 'puppet/ssl/certificate_signer'
require 'puppet/util'
# The class that knows how to sign certificates. It creates
# a 'special' SSL::Host whose name is 'ca', thus indicating
# that, well, it's the CA. There's some magic in the
# indirector/ssl_file terminus base class that does that
# for us.
# This class mostly just signs certs for us, but
# it can also be seen as a general interface into all of the
# SSL stuff.
class Puppet::SSL::CertificateAuthority
# We will only sign extensions on this whitelist, ever. Any CSR with a
# requested extension that we don't recognize is rejected, against the risk
# that it will introduce some security issue through our ignorance of it.
#
# Adding an extension to this whitelist simply means we will consider it
# further, not that we will always accept a certificate with an extension
# requested on this list.
RequestExtensionWhitelist = %w{subjectAltName}
require 'puppet/ssl/certificate_factory'
require 'puppet/ssl/inventory'
require 'puppet/ssl/certificate_revocation_list'
require 'puppet/ssl/certificate_authority/interface'
require 'puppet/ssl/certificate_authority/autosign_command'
require 'puppet/network/authstore'
class CertificateVerificationError < RuntimeError
attr_accessor :error_code
def initialize(code)
@error_code = code
end
end
def self.singleton_instance
@singleton_instance ||= new
end
class CertificateSigningError < RuntimeError
attr_accessor :host
def initialize(host)
@host = host
end
end
def self.ca?
# running as ca? - ensure boolean answer
!!(Puppet[:ca] && Puppet.run_mode.master?)
end
# If this process can function as a CA, then return a singleton instance.
def self.instance
ca? ? singleton_instance : nil
end
attr_reader :name, :host
# If autosign is configured, autosign the csr we are passed.
# @param csr [Puppet::SSL::CertificateRequest] The csr to sign.
# @return [Void]
# @api private
def autosign(csr)
if autosign?(csr)
Puppet.info "Autosigning #{csr.name}"
sign(csr.name)
end
end
# Determine if a CSR can be autosigned by the autosign store or autosign command
#
# @param csr [Puppet::SSL::CertificateRequest] The CSR to check
# @return [true, false]
# @api private
def autosign?(csr)
auto = Puppet[:autosign]
decider = case auto
when 'false', false, nil
AutosignNever.new
when 'true', true
AutosignAlways.new
else
file = Puppet::FileSystem::File.new(auto)
if file.executable?
Puppet::SSL::CertificateAuthority::AutosignCommand.new(auto)
elsif file.exist?
AutosignConfig.new(file)
else
AutosignNever.new
end
end
decider.allowed?(csr)
end
# Retrieves (or creates, if necessary) the certificate revocation list.
def crl
unless defined?(@crl)
unless @crl = Puppet::SSL::CertificateRevocationList.indirection.find(Puppet::SSL::CA_NAME)
@crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME)
@crl.generate(host.certificate.content, host.key.content)
Puppet::SSL::CertificateRevocationList.indirection.save(@crl)
end
end
@crl
end
# Delegates this to our Host class.
def destroy(name)
Puppet::SSL::Host.destroy(name)
end
# Generates a new certificate.
# @return Puppet::SSL::Certificate
def generate(name, options = {})
raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name)
# Pass on any requested subjectAltName field.
san = options[:dns_alt_names]
host = Puppet::SSL::Host.new(name)
host.generate_certificate_request(:dns_alt_names => san)
# CSR may have been implicitly autosigned, generating a certificate
# Or sign explicitly
host.certificate || sign(name, !!san)
end
# Generate our CA certificate.
def generate_ca_certificate
generate_password unless password?
host.generate_key unless host.key
# Create a new cert request. We do this specially, because we don't want
# to actually save the request anywhere.
request = Puppet::SSL::CertificateRequest.new(host.name)
# We deliberately do not put any subjectAltName in here: the CA
# certificate absolutely does not need them. --daniel 2011-10-13
request.generate(host.key)
# Create a self-signed certificate.
@certificate = sign(host.name, false, request)
# And make sure we initialize our CRL.
crl
end
def initialize
Puppet.settings.use :main, :ssl, :ca
@name = Puppet[:certname]
@host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name)
setup
end
# Retrieve (or create, if necessary) our inventory manager.
def inventory
@inventory ||= Puppet::SSL::Inventory.new
end
# Generate a new password for the CA.
def generate_password
pass = ""
20.times { pass += (rand(74) + 48).chr }
begin
Puppet.settings.setting(:capass).open('w') { |f| f.print pass }
rescue Errno::EACCES => detail
raise Puppet::Error, "Could not write CA password: #{detail}"
end
@password = pass
pass
end
# Lists the names of all signed certificates.
#
# @return [Array]
def list
list_certificates.collect { |c| c.name }
end
# Return all the certificate objects as found by the indirector
# API for PE license checking.
#
# Created to prevent the case of reading all certs from disk, getting
# just their names and verifying the cert for each name, which then
# causes the cert to again be read from disk.
#
# @author Jeff Weiss
# @api Puppet Enterprise Licensing
#
# @return [Array]
def list_certificates
Puppet::SSL::Certificate.indirection.search("*")
end
# Read the next serial from the serial file, and increment the
# file so this one is considered used.
def next_serial
serial = 1
Puppet.settings.setting(:serial).exclusive_open('a+') do |f|
f.rewind
serial = f.read.chomp.hex
if serial == 0
serial = 1
end
f.truncate(0)
f.rewind
# We store the next valid serial, not the one we just used.
f << "%04X" % (serial + 1)
end
serial
end
# Does the password file exist?
def password?
Puppet::FileSystem::File.exist? Puppet[:capass]
end
# Print a given host's certificate as text.
def print(name)
(cert = Puppet::SSL::Certificate.indirection.find(name)) ? cert.to_text : nil
end
# Revoke a given certificate.
def revoke(name)
raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
if cert = Puppet::SSL::Certificate.indirection.find(name)
serial = cert.content.serial
elsif name =~ /^0x[0-9A-Fa-f]+$/
serial = name.hex
elsif ! serial = inventory.serial(name)
raise ArgumentError, "Could not find a serial number for #{name}"
end
crl.revoke(serial, host.key.content)
end
# This initializes our CA so it actually works. This should be a private
# method, except that you can't any-instance stub private methods, which is
# *awesome*. This method only really exists to provide a stub-point during
# testing.
def setup
generate_ca_certificate unless @host.certificate
end
# Sign a given certificate request.
def sign(hostname, allow_dns_alt_names = false, self_signing_csr = nil)
# This is a self-signed certificate
if self_signing_csr
# # This is a self-signed certificate, which is for the CA. Since this
# # forces the certificate to be self-signed, anyone who manages to trick
# # the system into going through this path gets a certificate they could
# # generate anyway. There should be no security risk from that.
csr = self_signing_csr
cert_type = :ca
issuer = csr.content
else
allow_dns_alt_names = true if hostname == Puppet[:certname].downcase
unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname)
raise ArgumentError, "Could not find certificate request for #{hostname}"
end
cert_type = :server
issuer = host.certificate.content
# Make sure that the CSR conforms to our internal signing policies.
# This will raise if the CSR doesn't conform, but just in case...
check_internal_signing_policies(hostname, csr, allow_dns_alt_names) or
raise CertificateSigningError.new(hostname), "CSR had an unknown failure checking internal signing policies, will not sign!"
end
cert = Puppet::SSL::Certificate.new(hostname)
cert.content = Puppet::SSL::CertificateFactory.
build(cert_type, csr, issuer, next_serial)
signer = Puppet::SSL::CertificateSigner.new
signer.sign(cert.content, host.key.content)
Puppet.notice "Signed certificate request for #{hostname}"
# Add the cert to the inventory before we save it, since
# otherwise we could end up with it being duplicated, if
# this is the first time we build the inventory file.
inventory.add(cert)
# Save the now-signed cert. This should get routed correctly depending
# on the certificate type.
Puppet::SSL::Certificate.indirection.save(cert)
# And remove the CSR if this wasn't self signed.
Puppet::SSL::CertificateRequest.indirection.destroy(csr.name) unless self_signing_csr
cert
end
def check_internal_signing_policies(hostname, csr, allow_dns_alt_names)
# Reject unknown request extensions.
unknown_req = csr.request_extensions.reject do |x|
RequestExtensionWhitelist.include? x["oid"] or
Puppet::SSL::Oids.subtree_of?('ppRegCertExt', x["oid"], true) or
Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', x["oid"], true)
end
if unknown_req and not unknown_req.empty?
names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ")
raise CertificateSigningError.new(hostname), "CSR has request extensions that are not permitted: #{names}"
end
# Do not sign misleading CSRs
cn = csr.content.subject.to_a.assoc("CN")[1]
if hostname != cn
raise CertificateSigningError.new(hostname), "CSR subject common name #{cn.inspect} does not match expected certname #{hostname.inspect}"
end
if hostname !~ Puppet::SSL::Base::VALID_CERTNAME
raise CertificateSigningError.new(hostname), "CSR #{hostname.inspect} subject contains unprintable or non-ASCII characters"
end
# Wildcards: we don't allow 'em at any point.
#
# The stringification here makes the content visible, and saves us having
# to scrobble through the content of the CSR subject field to make sure it
# is what we expect where we expect it.
if csr.content.subject.to_s.include? '*'
raise CertificateSigningError.new(hostname), "CSR subject contains a wildcard, which is not allowed: #{csr.content.subject.to_s}"
end
unless csr.content.verify(csr.content.public_key)
raise CertificateSigningError.new(hostname), "CSR contains a public key that does not correspond to the signing key"
end
unless csr.subject_alt_names.empty?
# If you alt names are allowed, they are required. Otherwise they are
# disallowed. Self-signed certs are implicitly trusted, however.
unless allow_dns_alt_names
raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains subject alternative names (#{csr.subject_alt_names.join(', ')}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{csr.name}` to sign this request."
end
# If subjectAltNames are present, validate that they are only for DNS
# labels, not any other kind.
unless csr.subject_alt_names.all? {|x| x =~ /^DNS:/ }
raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' contains a subjectAltName outside the DNS label space: #{csr.subject_alt_names.join(', ')}. To continue, this CSR needs to be cleaned."
end
# Check for wildcards in the subjectAltName fields too.
if csr.subject_alt_names.any? {|x| x.include? '*' }
raise CertificateSigningError.new(hostname), "CSR '#{csr.name}' subjectAltName contains a wildcard, which is not allowed: #{csr.subject_alt_names.join(', ')} To continue, this CSR needs to be cleaned."
end
end
return true # good enough for us!
end
# Utility method for optionally caching the X509 Store for verifying a
# large number of certificates in a short amount of time--exactly the
# case we have during PE license checking.
#
# @example Use the cached X509 store
# x509store(:cache => true)
#
# @example Use a freshly create X509 store
# x509store
# x509store(:cache => false)
#
# @param [Hash] options the options used for retrieving the X509 Store
# @option options [Boolean] :cache whether or not to use a cached version
# of the X509 Store
#
# @return [OpenSSL::X509::Store]
def x509_store(options = {})
if (options[:cache])
return @x509store unless @x509store.nil?
@x509store = create_x509_store
else
create_x509_store
end
end
private :x509_store
# Creates a brand new OpenSSL::X509::Store with the appropriate
# Certificate Revocation List and flags
#
# @return [OpenSSL::X509::Store]
def create_x509_store
store = OpenSSL::X509::Store.new()
store.add_file(Puppet[:cacert])
store.add_crl(crl.content) if self.crl
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
if Puppet.settings[:certificate_revocation]
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL | OpenSSL::X509::V_FLAG_CRL_CHECK
end
store
end
private :create_x509_store
# Utility method which is API for PE license checking.
# This is used rather than `verify` because
# 1) We have already read the certificate from disk into memory.
# To read the certificate from disk again is just wasteful.
# 2) Because we're checking a large number of certificates against
# a transient CertificateAuthority, we can relatively safely cache
# the X509 Store that actually does the verification.
#
# Long running instances of CertificateAuthority will certainly
# want to use `verify` because it will recreate the X509 Store with
# the absolutely latest CRL.
#
# Additionally, this method explicitly returns a boolean whereas
# `verify` will raise an error if the certificate has been revoked.
#
# @author Jeff Weiss
# @api Puppet Enterprise Licensing
#
# @param cert [Puppet::SSL::Certificate] the certificate to check validity of
#
# @return [Boolean] true if signed, false if unsigned or revoked
def certificate_is_alive?(cert)
x509_store(:cache => true).verify(cert.content)
end
# Verify a given host's certificate. The certname is passed in, and
# the indirector will be used to locate the actual contents of the
# certificate with that name.
#
# @param name [String] certificate name to verify
#
# @raise [ArgumentError] if the certificate name cannot be found
# (i.e. doesn't exist or is unsigned)
# @raise [CertificateVerficationError] if the certificate has been revoked
#
# @return [Boolean] true if signed, there are no cases where false is returned
def verify(name)
unless cert = Puppet::SSL::Certificate.indirection.find(name)
raise ArgumentError, "Could not find a certificate for #{name}"
end
store = x509_store
raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content)
end
def fingerprint(name, md = :SHA256)
unless cert = Puppet::SSL::Certificate.indirection.find(name) || Puppet::SSL::CertificateRequest.indirection.find(name)
raise ArgumentError, "Could not find a certificate or csr for #{name}"
end
cert.fingerprint(md)
end
# List the waiting certificate requests.
def waiting?
Puppet::SSL::CertificateRequest.indirection.search("*").collect { |r| r.name }
end
# @api private
class AutosignAlways
def allowed?(csr)
true
end
end
# @api private
class AutosignNever
def allowed?(csr)
false
end
end
# @api private
class AutosignConfig
def initialize(config_file)
@config = config_file
end
def allowed?(csr)
autosign_store.allowed?(csr.name, '127.1.1.1')
end
private
def autosign_store
auth = Puppet::Network::AuthStore.new
@config.each_line do |line|
next if line =~ /^\s*#/
next if line =~ /^\s*$/
auth.allow(line.chomp)
end
auth
end
end
end
puppet-3.4.3/lib/puppet/ssl/certificate_signer.rb 0000644 0052762 0001160 00000001075 12300765631 021776 0 ustar jenkins jenkins # Take care of signing a certificate in a FIPS 140-2 compliant manner.
#
# @see http://projects.puppetlabs.com/issues/17295
#
# @api private
class Puppet::SSL::CertificateSigner
def initialize
if OpenSSL::Digest.const_defined?('SHA256')
@digest = OpenSSL::Digest::SHA256
elsif OpenSSL::Digest.const_defined?('SHA1')
@digest = OpenSSL::Digest::SHA1
else
raise Puppet::Error,
"No FIPS 140-2 compliant digest algorithm in OpenSSL::Digest"
end
@digest
end
def sign(content, key)
content.sign(key, @digest.new)
end
end
puppet-3.4.3/lib/puppet/ssl/certificate.rb 0000644 0052762 0001160 00000004135 12300765631 020427 0 ustar jenkins jenkins require 'puppet/ssl/base'
# Manage certificates themselves. This class has no
# 'generate' method because the CA is responsible
# for turning CSRs into certificates; we can only
# retrieve them from the CA (or not, as is often
# the case).
class Puppet::SSL::Certificate < Puppet::SSL::Base
# This is defined from the base class
wraps OpenSSL::X509::Certificate
extend Puppet::Indirector
indirects :certificate, :terminus_class => :file, :doc => < 'pp_uuid', 'value' => 'abcd'}]
#
# @return [Array String}>] An array of two element hashes,
# with key/value pairs for the extension's oid, and its value.
def custom_extensions
custom_exts = content.extensions.select do |ext|
Puppet::SSL::Oids.subtree_of?('ppRegCertExt', ext.oid) or
Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', ext.oid)
end
custom_exts.map { |ext| {'oid' => ext.oid, 'value' => ext.value} }
end
end
puppet-3.4.3/lib/puppet/ssl/certificate_request.rb 0000644 0052762 0001160 00000025574 12300765631 022211 0 ustar jenkins jenkins require 'puppet/ssl/base'
require 'puppet/ssl/certificate_signer'
# This class creates and manages X509 certificate signing requests.
#
# ## CSR attributes
#
# CSRs may contain a set of attributes that includes supplementary information
# about the CSR or information for the signed certificate.
#
# PKCS#9/RFC 2985 section 5.4 formally defines the "Challenge password",
# "Extension request", and "Extended-certificate attributes", but this
# implementation only handles the "Extension request" attribute. Other
# attributes may be defined on a CSR, but the RFC doesn't define behavior for
# any other attributes so we treat them as only informational.
#
# ## CSR Extension request attribute
#
# CSRs may contain an optional set of extension requests, which allow CSRs to
# include additional information that may be included in the signed
# certificate. Any additional information that should be copied from the CSR
# to the signed certificate MUST be included in this attribute.
#
# This behavior is dictated by PKCS#9/RFC 2985 section 5.4.2.
#
# @see http://tools.ietf.org/html/rfc2985 "RFC 2985 Section 5.4.2 Extension request"
#
class Puppet::SSL::CertificateRequest < Puppet::SSL::Base
wraps OpenSSL::X509::Request
extend Puppet::Indirector
# If auto-signing is on, sign any certificate requests as they are saved.
module AutoSigner
def save(instance, key = nil)
super
# Try to autosign the CSR.
if ca = Puppet::SSL::CertificateAuthority.instance
ca.autosign(instance)
end
end
end
indirects :certificate_request, :terminus_class => :file, :extend => AutoSigner, :doc => <>] :csr_attributes A hash
# of OIDs and values that are either a string or array of strings.
# @options opts [Array] :extension_requests A hash of
# certificate extensions to add to the CSR extReq attribute, excluding
# the Subject Alternative Names extension.
#
# @raise [Puppet::Error] If the generated CSR signature couldn't be verified
#
# @return [OpenSSL::X509::Request] The generated CSR
def generate(key, options = {})
Puppet.info "Creating a new SSL certificate request for #{name}"
# Support either an actual SSL key, or a Puppet key.
key = key.content if key.is_a?(Puppet::SSL::Key)
# If we're a CSR for the CA, then use the real ca_name, rather than the
# fake 'ca' name. This is mostly for backward compatibility with 0.24.x,
# but it's also just a good idea.
common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name
csr = OpenSSL::X509::Request.new
csr.version = 0
csr.subject = OpenSSL::X509::Name.new([["CN", common_name]])
csr.public_key = key.public_key
if options[:csr_attributes]
add_csr_attributes(csr, options[:csr_attributes])
end
if (ext_req_attribute = extension_request_attribute(options))
csr.add_attribute(ext_req_attribute)
end
signer = Puppet::SSL::CertificateSigner.new
signer.sign(csr, key)
raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for #{name} on the server" unless csr.verify(key.public_key)
@content = csr
Puppet.info "Certificate Request fingerprint (#{digest.name}): #{digest.to_hex}"
@content
end
# Return the set of extensions requested on this CSR, in a form designed to
# be useful to Ruby: an array of hashes. Which, not coincidentally, you can pass
# successfully to the OpenSSL constructor later, if you want.
#
# @return [Array String}>] An array of two or three element
# hashes, with key/value pairs for the extension's oid, its value, and
# optionally its critical state.
def request_extensions
raise Puppet::Error, "CSR needs content to extract fields" unless @content
# Prefer the standard extReq, but accept the Microsoft specific version as
# a fallback, if the standard version isn't found.
attribute = @content.attributes.find {|x| x.oid == "extReq" }
attribute ||= @content.attributes.find {|x| x.oid == "msExtReq" }
return [] unless attribute
extensions = unpack_extension_request(attribute)
index = -1
extensions.map do |ext_values|
index += 1
context = "#{attribute.oid} extension index #{index}"
# OK, turn that into an extension, to unpack the content. Lovely that
# we have to swap the order of arguments to the underlying method, or
# perhaps that the ASN.1 representation chose to pack them in a
# strange order where the optional component comes *earlier* than the
# fixed component in the sequence.
case ext_values.length
when 2
ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[1].value)
{ "oid" => ev.oid, "value" => ev.value }
when 3
ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[2].value, ext_values[1].value)
{ "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? }
else
raise Puppet::Error, "In #{attribute.oid}, expected extension record #{index} to have two or three items, but found #{ext_values.length}"
end
end
end
def subject_alt_names
@subject_alt_names ||= request_extensions.
select {|x| x["oid"] == "subjectAltName" }.
map {|x| x["value"].split(/\s*,\s*/) }.
flatten.
sort.
uniq
end
# Return all user specified attributes attached to this CSR as a hash. IF an
# OID has a single value it is returned as a string, otherwise all values are
# returned as an array.
#
# The format of CSR attributes is specified in PKCS#10/RFC 2986
#
# @see http://tools.ietf.org/html/rfc2986 "RFC 2986 Certification Request Syntax Specification"
#
# @api public
#
# @return [Hash]
def custom_attributes
x509_attributes = @content.attributes.reject do |attr|
PRIVATE_CSR_ATTRIBUTES.include? attr.oid
end
x509_attributes.map do |attr|
{"oid" => attr.oid, "value" => attr.value.first.value}
end
end
private
# Exclude OIDs that may conflict with how Puppet creates CSRs.
#
# We only have nominal support for Microsoft extension requests, but since we
# ultimately respect that field when looking for extension requests in a CSR
# we need to prevent that field from being written to directly.
PRIVATE_CSR_ATTRIBUTES = [
'extReq', '1.2.840.113549.1.9.14',
'msExtReq', '1.3.6.1.4.1.311.2.1.14',
]
def add_csr_attributes(csr, csr_attributes)
csr_attributes.each do |oid, value|
begin
if PRIVATE_CSR_ATTRIBUTES.include? oid
raise ArgumentError, "Cannot specify CSR attribute #{oid}: conflicts with internally used CSR attribute"
end
encoded = OpenSSL::ASN1::PrintableString.new(value.to_s)
attr_set = OpenSSL::ASN1::Set.new([encoded])
csr.add_attribute(OpenSSL::X509::Attribute.new(oid, attr_set))
Puppet.debug("Added csr attribute: #{oid} => #{attr_set.inspect}")
rescue OpenSSL::X509::AttributeError => e
raise Puppet::Error, "Cannot create CSR with attribute #{oid}: #{e.message}"
end
end
end
private
PRIVATE_EXTENSIONS = [
'subjectAltName', '2.5.29.17',
]
# @api private
def extension_request_attribute(options)
extensions = []
if options[:extension_requests]
options[:extension_requests].each_pair do |oid, value|
begin
if PRIVATE_EXTENSIONS.include? oid
raise Puppet::Error, "Cannot specify CSR extension request #{oid}: conflicts with internally used extension request"
end
ext = OpenSSL::X509::Extension.new(oid, value.to_s, false)
extensions << ext
rescue OpenSSL::X509::ExtensionError => e
raise Puppet::Error, "Cannot create CSR with extension request #{oid}: #{e.message}"
end
end
end
if options[:dns_alt_names]
names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name]
names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ")
alt_names_ext = extension_factory.create_extension("subjectAltName", names, false)
extensions << alt_names_ext
end
unless extensions.empty?
seq = OpenSSL::ASN1::Sequence(extensions)
ext_req = OpenSSL::ASN1::Set([seq])
OpenSSL::X509::Attribute.new("extReq", ext_req)
end
end
# Unpack the extReq attribute into an array of Extensions.
#
# The extension request attribute is structured like
# `Set[Sequence[Extensions]]` where the outer Set only contains a single
# sequence.
#
# In addition the Ruby implementation of ASN1 requires that all ASN1 values
# contain a single value, so Sets and Sequence have to contain an array
# that in turn holds the elements. This is why we have to unpack an array
# every time we unpack a Set/Seq.
#
# @see http://tools.ietf.org/html/rfc2985#ref-10 5.4.2 CSR Extension Request structure
# @see http://tools.ietf.org/html/rfc5280 4.1 Certificate Extension structure
#
# @api private
#
# @param attribute [OpenSSL::X509::Attribute] The X509 extension request
#
# @return [Array>] A array of arrays containing the extension
# OID the critical state if present, and the extension value.
def unpack_extension_request(attribute)
unless attribute.value.is_a? OpenSSL::ASN1::Set
raise Puppet::Error, "In #{attribute.oid}, expected Set but found #{attribute.value.class}"
end
unless attribute.value.value.is_a? Array
raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] but found #{attribute.value.value.class}"
end
unless attribute.value.value.size == 1
raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] with one value but found #{attribute.value.value.size} elements"
end
unless attribute.value.value.first.is_a? OpenSSL::ASN1::Sequence
raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[...]]], but found #{extension.class}"
end
unless attribute.value.value.first.value.is_a? Array
raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[Array[...]]]], but found #{extension.value.class}"
end
extensions = attribute.value.value.first.value
extensions.map(&:value)
end
end
puppet-3.4.3/lib/puppet/ssl/validator.rb 0000644 0052762 0001160 00000003102 12300765631 020123 0 ustar jenkins jenkins require 'openssl'
# API for certificate verification
#
# @api public
class Puppet::SSL::Validator
# Factory method for creating an instance of a null/no validator.
# This method does not have to be implemented by concrete implementations of this API.
#
# @return [Puppet::SSL::Validator] produces a validator that performs no validation
#
# @api public
#
def self.no_validator()
@@no_validator_cache ||= Puppet::SSL::Validator::NoValidator.new()
end
# Factory method for creating an instance of the default Puppet validator.
# This method does not have to be implemented by concrete implementations of this API.
#
# @return [Puppet::SSL::Validator] produces a validator that performs no validation
#
# @api public
#
def self.default_validator()
Puppet::SSL::Validator::DefaultValidator.new()
end
# Array of peer certificates
# @return [Array] peer certificates
#
# @api public
#
def peer_certs
raise NotImplementedError, "Concrete class should have implemented this method"
end
# Contains the result of validation
# @return [Array, nil] nil, empty Array, or Array with messages
#
# @api public
#
def verify_errors
raise NotImplementedError, "Concrete class should have implemented this method"
end
# Registers the connection to validate.
#
# @param [Net::HTTP] connection The connection to validate
#
# @return [void]
#
# @api public
#
def setup_connection(connection)
raise NotImplementedError, "Concrete class should have implemented this method"
end
end
puppet-3.4.3/lib/puppet/ssl/certificate_revocation_list.rb 0000644 0052762 0001160 00000006406 12300765631 023716 0 ustar jenkins jenkins require 'puppet/ssl/base'
require 'puppet/indirector'
# Manage the CRL.
class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base
FIVE_YEARS = 5 * 365*24*60*60
wraps OpenSSL::X509::CRL
extend Puppet::Indirector
indirects :certificate_revocation_list, :terminus_class => :file, :doc => <