puppet-3.4.3/0000755005276200011600000000000012300765640012731 5ustar jenkinsjenkinspuppet-3.4.3/examples/0000755005276200011600000000000012300765640014547 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/0000755005276200011600000000000012300765640015637 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/site.pp0000644005276200011600000000005412300765631017143 0ustar jenkinsjenkinsnode default { hiera_include('classes') } puppet-3.4.3/examples/hiera/README.md0000644005276200011600000001012112300765631017111 0ustar jenkinsjenkinsA 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/0000755005276200011600000000000012300765640016412 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/etc/puppet.conf0000644005276200011600000000003712300765631020576 0ustar jenkinsjenkins[main] modulepath = modules puppet-3.4.3/examples/hiera/etc/hieradb/0000755005276200011600000000000012300765640020010 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/etc/hieradb/development.yaml0000644005276200011600000000004012300765631023210 0ustar jenkinsjenkins--- classes: users::development puppet-3.4.3/examples/hiera/etc/hieradb/dc1.yaml0000644005276200011600000000013212300765631021337 0ustar jenkinsjenkins--- ntpservers: - ntp1.dc1.example.com - ntp2.dc1.example.com classes: - users::dc1 puppet-3.4.3/examples/hiera/etc/hieradb/common.yaml0000644005276200011600000000005312300765631022162 0ustar jenkinsjenkinsclasses: - users::common - ntp::config puppet-3.4.3/examples/hiera/etc/hiera.yaml0000644005276200011600000000024012300765631020362 0ustar jenkinsjenkins--- :backends: - yaml - puppet :hierarchy: - "%{location}" - "%{environment}" - common :yaml: :datadir: etc/hieradb :puppet: :datasource: data puppet-3.4.3/examples/hiera/modules/0000755005276200011600000000000012300765640017307 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/ntp/0000755005276200011600000000000012300765640020110 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/ntp/templates/0000755005276200011600000000000012300765640022106 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/ntp/templates/ntp.conf.erb0000644005276200011600000000011512300765631024322 0ustar jenkinsjenkins<% [ntpservers].flatten.each do |server| -%> server <%= server %> <% end -%> puppet-3.4.3/examples/hiera/modules/ntp/manifests/0000755005276200011600000000000012300765640022101 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/ntp/manifests/config.pp0000644005276200011600000000031712300765631023710 0ustar jenkinsjenkins# 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.pp0000644005276200011600000000020412300765631023347 0ustar jenkinsjenkins# 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/0000755005276200011600000000000012300765640020220 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/data/manifests/0000755005276200011600000000000012300765640022211 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/data/manifests/common.pp0000644005276200011600000000021012300765631024033 0ustar jenkinsjenkins# 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/0000755005276200011600000000000012300765640020450 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/users/manifests/0000755005276200011600000000000012300765640022441 5ustar jenkinsjenkinspuppet-3.4.3/examples/hiera/modules/users/manifests/common.pp0000644005276200011600000000010612300765631024267 0ustar jenkinsjenkins# notifies class users::common { notify{'Adding users::common': } } puppet-3.4.3/examples/hiera/modules/users/manifests/dc1.pp0000644005276200011600000000010012300765631023440 0ustar jenkinsjenkins# notifies class users::dc1 { notify{'Adding users::dc1': } } puppet-3.4.3/examples/hiera/modules/users/manifests/development.pp0000644005276200011600000000012012300765631025315 0ustar jenkinsjenkins# notifies class users::development { notify{'Adding users::development': } } puppet-3.4.3/conf/0000755005276200011600000000000012300765640013656 5ustar jenkinsjenkinspuppet-3.4.3/conf/tagmail.conf0000644005276200011600000000122612300765631016144 0ustar jenkinsjenkins# 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.conf0000644005276200011600000000266612300765631016705 0ustar jenkinsjenkins# 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.conf0000644005276200011600000001004512300765631015466 0ustar jenkinsjenkins# 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/0000755005276200011600000000000012300765640014056 5ustar jenkinsjenkinspuppet-3.4.3/tasks/parser.rake0000644005276200011600000000017212300765632016217 0ustar jenkinsjenkins 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.rake0000644005276200011600000000146112300765632015320 0ustar jenkinsjenkinsrequire '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.rake0000644005276200011600000000645412300765632016527 0ustar jenkinsjenkins# 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.rake0000644005276200011600000000547112300765632016664 0ustar jenkinsjenkinsrequire '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/LICENSE0000644005276200011600000000127612300765631013744 0ustar jenkinsjenkins 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/0000755005276200011600000000000012300765640013477 5ustar jenkinsjenkinspuppet-3.4.3/lib/puppet/0000755005276200011600000000000012300765640015014 5ustar jenkinsjenkinspuppet-3.4.3/lib/puppet/provider.rb0000644005276200011600000006164712300765631017211 0ustar jenkinsjenkins# 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.rb0000644005276200011600000000355512300765631016646 0ustar jenkinsjenkinsmodule 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/0000755005276200011600000000000012300765640016512 5ustar jenkinsjenkinspuppet-3.4.3/lib/puppet/reports/http.rb0000644005276200011600000000157612300765631020027 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000001337212300765631020463 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000366712300765631020207 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000050612300765631017621 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000001025712300765631020655 0ustar jenkinsjenkinsPuppet::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.rb0000644005276200011600000000475312300765631017514 0ustar jenkinsjenkins# 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/0000755005276200011600000000000012300765640015615 5ustar jenkinsjenkinspuppet-3.4.3/lib/puppet/ssl/host.rb0000644005276200011600000002646112300765631017130 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000376212300765631021021 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000004066112300765631022543 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000107512300765631021776 0ustar jenkinsjenkins# 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.rb0000644005276200011600000000413512300765631020427 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000002557412300765631022211 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000310212300765631020123 0ustar jenkinsjenkinsrequire '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.rb0000644005276200011600000000640612300765631023716 0ustar jenkinsjenkinsrequire '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 => <