diff --git a/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb b/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb index f774eca05..a9ead6b65 100644 --- a/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb +++ b/acceptance/tests/ticket_5477_master_not_dectect_sitepp.rb @@ -1,33 +1,33 @@ # In 2.6, compile does not fail when site.pp does not exist. # # However, if a catalog is compiled when site.pp does not exist, # puppetmaster does not detect when site.pp is created. This requires a restart # test_name "Ticket 5477, Puppet Master does not detect newly created site.pp file" manifest_file = "/tmp/missing_site-5477-#{$$}.pp" on master, "rm -f #{manifest_file}" -with_master_running_on(master, "--manifest #{manifest_file} --certdnsnames=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --filetimeout 1") do +with_master_running_on(master, "--manifest #{manifest_file} --dns-alt-names=\"puppet:$(hostname -s):$(hostname -f)\" --verbose --filetimeout 1") do # Run test on Agents step "Agent: agent --test" on agents, puppet_agent("--test --server #{master}") # Create a new site.pp step "Master: create basic site.pp file" create_remote_file master, manifest_file, "notify{ticket_5477_notify:}" on master, "chmod 644 #{manifest_file}" sleep 3 step "Agent: puppet agent --test" agents.each do |host| on(host, puppet_agent("--test --server #{master}"), :acceptable_exit_codes => [2]) do assert_match(/ticket_5477_notify/, stdout, "#{host}: Site.pp not detected on Puppet Master") end end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 0315c61de..5690669cc 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,892 +1,917 @@ # The majority of the system configuration parameters are set in this file. module Puppet setdefaults(:main, :confdir => [Puppet.run_mode.conf_dir, "The main Puppet configuration directory. The default for this parameter is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory."], :vardir => [Puppet.run_mode.var_dir, "Where Puppet stores dynamic and growing data. The default for this parameter is calculated specially, like `confdir`_."], :name => [Puppet.application_name.to_s, "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`."], :run_mode => [Puppet.run_mode.name.to_s, "The effective 'run mode' of the application: master, agent, or user."] ) setdefaults(:main, :logdir => Puppet.run_mode.logopts) setdefaults(:main, :trace => [false, "Whether to print stack traces on some errors"], :autoflush => { :default => false, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => ["daemon", "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up."], :statedir => { :default => "$vardir/state", :mode => 01755, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => Puppet.run_mode.run_dir, :mode => 01777, :desc => "Where Puppet PID files are kept." }, :genconfig => [false, "Whether to just print a configuration to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :genmanifest => [false, "Whether to just print a manifest to stdout and exit. Only makes sense when used interactively. Takes into account arguments specified on the CLI."], :configprint => ["", "Print the value of a specific configuration parameter. If a parameter is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'. This feature is only available in Puppet versions higher than 0.18.4."], :color => { :default => (Puppet.features.microsoft_windows? ? "false" : "ansi"), :type => :setting, :desc => "Whether to use colors when logging to the console. Valid values are `ansi` (equivalent to `true`), `html` (mostly used during testing with TextMate), and `false`, which produces no color.", }, :mkusers => [false, "Whether to create the necessary user and group that puppet agent will run as."], :manage_internal_file_permissions => [true, "Whether Puppet should manage the owner, group, and mode of files it uses internally" ], :onetime => {:default => false, :desc => "Run the configuration once, rather than as a long-running daemon. This is useful for interactively running puppetd.", :short => 'o' }, :path => {:default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) %w{/usr/sbin /sbin}.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => {:default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby's search path", :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => [false, "A parameter that can be used in commit hooks, since it enables you to parse-check a single file rather than requiring that all files exist."], :authconfig => [ "$confdir/namespaceauth.conf", "The configuration file that defines the rights to the different namespaces and methods. This can be used as a coarse-grained authorization system for both `puppet agent` and `puppet master`." ], :environment => {:default => "production", :desc => "The environment Puppet is running in. For clients (e.g., `puppet agent`) this determines the environment itself, which is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes we know nothing about." }, :diff_args => ["-u", "Which arguments to pass to the diff command when printing differences between files."], :diff => ["diff", "Which diff command to use when printing differences between files."], :show_diff => [false, "Whether to print a contextual diff when files are being replaced. The diff is printed on stdout, so this option is meaningless unless you are running Puppet interactively. This feature currently requires the `diff/lcs` Ruby library."], :daemonize => { :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Send the process into the background. This is the default.", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => [4294967290, "The maximum allowed UID. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens."], :route_file => ["$confdir/routes.yaml", "The YAML file containing indirector route configuration."], :node_terminus => ["plain", "Where to find information about nodes."], :catalog_terminus => ["compiler", "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store."], :facts_terminus => { :default => Puppet.application_name.to_s == "master" ? 'yaml' : 'facter', :desc => "The node facts terminus.", :hook => proc do |value| require 'puppet/node/facts' if value.to_s == "rest" Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => [ "$facts_terminus", "Should usually be the same as the facts terminus" ], :httplog => { :default => "$logdir/http.log", :owner => "root", :mode => 0640, :desc => "Where the puppet agent web server logs." }, :http_proxy_host => ["none", "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy."], :http_proxy_port => [3128, "The HTTP proxy port to use for outgoing connections"], :filetimeout => [ 15, "The minimum time to wait (in seconds) between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk." ], :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], :queue_type => ["stomp", "Which type of queue to use for asynchronous processing."], :queue_source => ["stomp://localhost:61613/", "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1"], :async_storeconfigs => {:default => false, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppetqd` be running and that 'PSON' support for ruby be installed.", :hook => proc do |value| if value # This reconfigures the terminii for Node, Facts, and Catalog Puppet.settings[:storeconfigs] = true # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => {:default => false, :desc => "Boolean; wether storeconfigs store in the database only the facts and exported resources. If true, then storeconfigs performance will be higher and still allow exported/collected resources, but other usage external to Puppet might not work", :hook => proc do |value| Puppet.settings[:storeconfigs] = true if value end }, :config_version => ["", "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server."], :zlib => [true, "Boolean; whether to use the zlib library", ], :prerun_command => ["", "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail."], :postrun_command => ["", "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run."], :freeze_main => [false, "Freezes the 'main' class, disallowing any code to be added to it. This essentially means that you can't have any code outside of a node, class, or definition other than in the site manifest."] ) hostname = Facter["hostname"].value domain = Facter["domain"].value if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end Puppet.setdefaults( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. :certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_on_define => true, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, - :certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list. - If it's anything other than an empty string, it will be used as an alias in the created - certificate. Only the locally generated master certificate gets an alias set up, and only if this is set."], + :certdnsnames => { + :default => '', + :hook => proc do |value| + unless value.nil? or value == '' then + Puppet.warning < < { :default => "$ssldir/certs", :owner => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :mode => 0771, :owner => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :owner => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :owner => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :mode => 0750, :owner => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :mode => 0750, :owner => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :mode => 0640, :owner => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :mode => 0600, :owner => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :mode => 0644, :owner => "service", :desc => "Where each client stores the CA certificate." }, :hostcrl => { :default => "$ssldir/crl.pem", :mode => 0644, :owner => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => [true, "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work."] ) setdefaults( :ca, :ca_name => ["Puppet CA: $certname", "The name to use the Certificate Authority certificate."], :cadir => { :default => "$ssldir/ca", :owner => "service", :group => "service", :mode => 0770, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :owner => "service", :group => "service", :mode => 0660, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :owner => "service", :group => "service", :mode => 0660, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :owner => "service", :group => "service", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :owner => "service", :group => "service", :mode => 0664, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", :hook => proc do |value| if value == 'false' Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" end end }, :caprivatedir => { :default => "$cadir/private", :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :owner => "service", :group => "service", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :owner => "service", :group => "service", :mode => 0660, :desc => "Where the CA stores the password for the private key" }, :serial => { :default => "$cadir/serial", :owner => "service", :group => "service", :mode => 0644, :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :mode => 0644, :desc => "Whether to enable autosign. Valid values are true (which autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, :allow_duplicate_certs => [false, "Whether to allow a new certificate request to overwrite an existing certificate."], :ca_days => ["", "How long a certificate should be valid. This parameter is deprecated, use ca_ttl instead"], :ca_ttl => ["5y", "The default TTL for new certificates; valid values must be an integer, optionally followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), or 's' (seconds). The unit defaults to seconds. If this parameter is set, ca_days is ignored. Examples are '3600' (one hour) and '1825d', which is the same as '5y' (5 years) "], :ca_md => ["md5", "The type of hash used in certificates."], :req_bits => [2048, "The bit length of the certificates."], :keylength => [1024, "The bit length of keys."], :cert_inventory => { :default => "$cadir/inventory.txt", :mode => 0644, :owner => "service", :group => "service", :desc => "A Complete listing of all certificates" } ) # Define the config default. setdefaults( Puppet.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], :pidfile => ["$rundir/$name.pid", "The pid file"], :bindaddress => ["", "The address a listening server should bind to. Mongrel servers default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."], :servertype => {:default => "webrick", :desc => "The type of server to use. Currently supported options are webrick and mongrel. If you use mongrel, you will need a proxy in front of the process or processes, since Mongrel cannot speak SSL.", :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set. :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" } } ) setdefaults(:master, :user => ["puppet", "The user puppet master should run as."], :group => ["puppet", "The group puppet master should run as."], :manifestdir => ["$confdir/manifests", "Where puppet master looks for its manifests."], :manifest => ["$manifestdir/site.pp", "The entry-point manifest for puppet master."], :code => ["", "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable"], :masterlog => { :default => "$logdir/puppetmaster.log", :owner => "service", :group => "service", :mode => 0660, :desc => "Where puppet master logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, :masterport => [8140, "Which port puppet master listens on."], :node_name => ["cert", "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)"], :bucketdir => { :default => "$vardir/bucket", :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => [ "$confdir/auth.conf", "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`." ], :ca => [true, "Wether the master should function as a certificate authority."], :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :desc => "The search path for modules as a list of directories separated by the '#{File::PATH_SEPARATOR}' character.", :type => :setting # We don't want this to be considered a file, since it's multiple files. }, + :master_dns_alt_names => ['', < ["HTTP_X_CLIENT_DN", "The header containing an authenticated client's SSL DN. Only used with Mongrel. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], :ssl_client_verify_header => ["HTTP_X_CLIENT_VERIFY", "The header containing the status message of the client verification. Only used with Mongrel. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information."], # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => {:default => "$vardir/yaml", :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => {:default => "$vardir/server_data", :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => ["store", "The list of reports to generate. All reports are looked for in `puppet/reports/name.rb`, and multiple report names should be comma-separated (whitespace is okay)." ], :reportdir => {:default => "$vardir/reports", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reporturl => ["http://localhost:3000/reports/upload", "The URL used by the http reports processor to send reports"], :fileserverconfig => ["$confdir/fileserver.conf", "Where the fileserver configuration is stored."], :strict_hostname_checking => [false, "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs."] ) setdefaults(:metrics, :rrddir => {:default => "$vardir/rrd", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => ["$runinterval", "How often RRD should expect data. This should match how often the hosts report back to the server."] ) setdefaults(:device, :devicedir => {:default => "$vardir/devices", :mode => "750", :desc => "The root directory of devices' $vardir"}, :deviceconfig => ["$confdir/device.conf","Path to the device config file for puppet device"] ) setdefaults(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :owner => "root", :mode => 0660, :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :statefile => { :default => "$statedir/state.yaml", :mode => 0660, :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :clientyamldir => {:default => "$vardir/client_yaml", :mode => "750", :desc => "The directory in which client-side YAML data is stored."}, :client_datadir => {:default => "$vardir/client_data", :mode => "750", :desc => "The directory in which serialized data is stored on the client."}, :classfile => { :default => "$statedir/classes.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :owner => "root", :mode => 0640, :desc => "The log file for puppet agent. This is generally not used." }, :server => ["puppet", "The server to which server puppet agent should connect"], :ignoreschedules => [false, "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs."], :puppetport => [8139, "Which port puppet agent listens on."], :noop => [false, "Whether puppet agent should be run in noop mode."], :runinterval => [1800, # 30 minutes "How often puppet agent applies the client configuration; in seconds. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option."], :listen => [false, "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints."], :ca_server => ["$server", "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale."], :ca_port => ["$masterport", "The port to use for the certificate authority."], :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings[:preferred_serialization_format] = value end } }, :preferred_serialization_format => ["pson", "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it."], :puppetdlockfile => [ "$statedir/puppetdlock", "A lock file to temporarily stop puppet agent from doing anything."], :usecacheonfailure => [true, "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known-good one." ], :use_cached_catalog => [false, "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired."], :ignorecache => [false, "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes." ], :downcasefacts => [false, "Whether facts should be made all lowercase when sent to the server."], :dynamicfacts => ["memorysize,memoryfree,swapsize,swapfree", "Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated."], :splaylimit => ["$runinterval", "The maximum time to delay before runs. Defaults to being the same as the run interval."], :splay => [false, "Whether to sleep for a pseudo-random (but consistent) amount of time before a run."], :clientbucketdir => { :default => "$vardir/clientbucket", :mode => 0750, :desc => "Where FileBucket files are stored locally." }, :configtimeout => [120, "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too many clients contact the server at one time." ], :reportserver => { :default => "$server", :call_on_define => false, :desc => "(Deprecated for 'report_server') The server to which to send transaction reports.", :hook => proc do |value| Puppet.settings[:report_server] = value if value end }, :report_server => ["$server", "The server to send transaction reports to." ], :report_port => ["$masterport", "The port to communicate with the report_server." ], :inventory_server => ["$server", "The server to send facts to." ], :inventory_port => ["$masterport", "The port to communicate with the inventory_server." ], :report => [true, "Whether to send reports after every transaction." ], :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :mode => 0660, :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :mode => 0660, :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => [false, "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick)."], :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."], :http_compression => [false, "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs."] ) setdefaults(:inspect, :archive_files => [false, "During an inspect run, whether to archive files whose contents are audited to a file bucket."], :archive_file_server => ["$server", "During an inspect run, the file bucket server to archive files to if archive_files is set."] ) # Plugin information. setdefaults( :main, :plugindest => ["$libdir", "Where Puppet should store plugins that it pulls down from the central server."], :pluginsource => ["puppet://$server/plugins", "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here."], :pluginsync => [false, "Whether plugins should be synced with the central server."], :pluginsignore => [".svn CVS .git", "What files to ignore when pulling down plugins."] ) # Central fact information. setdefaults( :main, :factpath => {:default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be colon-separated, like normal PATH variables.", :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. :type => :setting, # Don't consider it a file, because it could be multiple colon-separated files :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }}, :factdest => ["$vardir/facts/", "Where Puppet should store facts that it pulls down from the central server."], :factsource => ["puppet://$server/facts/", "From where to retrieve facts. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here."], :factsync => [false, "Whether facts should be synced with the central server."], :factsignore => [".svn CVS", "What files to ignore when pulling down facts."] ) setdefaults( :tagmail, :tagmap => ["$confdir/tagmail.conf", "The mapping between reporting tags and email addresses."], :sendmail => [which('sendmail') || '', "Where to find the sendmail binary with which to send email."], :reportfrom => ["report@" + [Facter["hostname"].value, Facter["domain"].value].join("."), "The 'from' email address for the reports."], :smtpserver => ["none", "The server through which to send email reports."] ) setdefaults( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :mode => 0660, :owner => "service", :group => "service", :desc => "The database cache for client configurations. Used for querying within the language." }, :dbadapter => [ "sqlite3", "The type of database to use." ], :dbmigrate => [ false, "Whether to automatically migrate the database." ], :dbname => [ "puppet", "The name of the database to use." ], :dbserver => [ "localhost", "The database server for caching. Only used when networked databases are used."], :dbport => [ "", "The database password for caching. Only used when networked databases are used."], :dbuser => [ "puppet", "The database user for caching. Only used when networked databases are used."], :dbpassword => [ "puppet", "The database password for caching. Only used when networked databases are used."], :dbconnections => [ '', "The number of database connections for networked databases. Will be ignored unless the value is a positive integer."], :dbsocket => [ "", "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string."], :railslog => {:default => "$logdir/rails.log", :mode => 0600, :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent" }, :rails_loglevel => ["info", "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`."] ) setdefaults( :couchdb, :couchdb_url => ["http://127.0.0.1:5984/puppet", "The url where the puppet couchdb database will be created"] ) setdefaults( :transaction, :tags => ["", "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated."], :evaltrace => [false, "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done."], :summarize => [false, "Whether to print a transaction summary." ] ) setdefaults( :main, :external_nodes => ["none", "An external command that can produce node information. The output must be a YAML dump of a hash, and that hash must have one or both of `classes` and `parameters`, where `classes` is an array and `parameters` is a hash. For unknown nodes, the commands should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases."]) setdefaults( :ldap, :ldapnodes => [false, "Whether to search for node configurations in LDAP. See http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information."], :ldapssl => [false, "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side."], :ldaptls => [false, "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side."], :ldapserver => ["ldap", "The LDAP server. Only used if `ldapnodes` is enabled."], :ldapport => [389, "The LDAP port. Only used if `ldapnodes` is enabled."], :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", "The search string used to find an LDAP node."], :ldapclassattrs => ["puppetclass", "The LDAP attributes to use to define Puppet classes. Values should be comma-separated."], :ldapstackedattrs => ["puppetvar", "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated."], :ldapattrs => ["all", "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes."], :ldapparentattr => ["parentnode", "The attribute to use to define the parent node."], :ldapuser => ["", "The user to use to connect to LDAP. Must be specified as a full DN."], :ldappassword => ["", "The password to use to connect to LDAP."], :ldapbase => ["", "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory."] ) setdefaults(:master, :storeconfigs => { :default => false, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_on_define => true, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value Puppet.settings[:async_storeconfigs] or Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Node.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) # This doesn't actually work right now. setdefaults( :parser, :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], :templatedir => ["$vardir/templates", "Where Puppet looks for template files. Can be a list of colon-seperated directories." ] ) setdefaults( :puppetdoc, :document_all => [false, "Document all resources"] ) end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index d76747ae2..e2c526513 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -1,341 +1,342 @@ require 'puppet/indirector' require 'puppet/ssl' require 'puppet/ssl/key' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_revocation_list' # 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 attr_reader :name attr_accessor :ca attr_writer :key, :certificate, :certificate_request # This accessor is used in instances for indirector requests to hold desired state attr_accessor :desired_state def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end def self.reset @localhost = nil end # This is the constant that people will use to mark that a given host is # a certificate authority. def self.ca_name CA_NAME end class << self attr_reader :ca_location end # Configure how our various classes interact with their various terminuses. def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus CertificateRevocationList.indirection.terminus_class = terminus host_map = {:ca => :file, :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 => [:file] } # 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 def this_csr_is_for_a_local_master # If this is for another machine, we are *not* generating a master cert. # At least, not in an automatic fashion. return false unless name == Puppet[:certname].downcase # If we are the CA, we are generating a master cert. return true if Puppet::SSL::CertificateAuthority.ca? # If we are running in master run_mode, we say that our own CSR should be # treated as a master cert. We checked that the CSR is for us earlier. return true if Puppet.run_mode.master? # ...otherwise, no. return false end # Our certificate request requires the key but that's all. def generate_certificate_request(options = {}) generate_key unless key # If this is for the current machine... if this_csr_is_for_a_local_master - # ...add our configured certdnsnames - have_certdnsnames = (Puppet[:certdnsnames] and Puppet[:certdnsnames] != '') + # ...add our configured dns-alt-names + have_dns_alt_names = (Puppet[:master_dns_alt_names] and + Puppet[:master_dns_alt_names] != '') - if options[:subject_alt_name] and have_certdnsnames - raise ArgumentError, "When generating the CSR for #{name}, command line subjectAltName and configured certdnsnames are both set. Omit one or the other." + if options[:subject_alt_name] and have_dns_alt_names + raise ArgumentError, "When generating the CSR for #{name}, command line subjectAltName and configured master_dns_alt_names are both set. Omit one or the other." end - options[:subject_alt_name] = Puppet[:certdnsnames] + options[:subject_alt_name] = Puppet[:master_dns_alt_names] 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) unless certificate_matches_key? raise Puppet::Error, "Retrieved certificate does not match private key; please remove certificate from server and regenerate it with the current key" end end @certificate end def certificate_matches_key? return false unless key return false unless certificate certificate.content.check_private_key(key.content) end # Generate all necessary parts of our ssl host. def generate generate_key unless key generate_certificate_request unless certificate_request # If we can get a CA instance, then we're a valid CA, and we # should use it to sign our request; else, just try to read # the cert. if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance ca.sign(self.name, true) end end def initialize(name = nil) @name = (name || Puppet[:certname]).downcase @key = @certificate = @certificate_request = nil @ca = (name == self.class.ca_name) end # Extract the public key from the private key. def public_key key.content.public_key end # Create/return a store that uses our SSL info to validate # connections. def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) unless @ssl_store @ssl_store = OpenSSL::X509::Store.new @ssl_store.purpose = purpose # Use the file path here, because we don't want to cause # a lookup in the middle of setting our ssl connection. @ssl_store.add_file(Puppet[:localcacert]) # If there's a CRL, add it to our store. if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] @ssl_store.add_crl(crl.content) end return @ssl_store end @ssl_store end def to_pson(*args) my_cert = Puppet::SSL::Certificate.indirection.find(name) pson_hash = { :name => name } my_state = state pson_hash[:state] = my_state pson_hash[:desired_state] = desired_state if desired_state if my_state == 'requested' pson_hash[:fingerprint] = certificate_request.fingerprint else pson_hash[:fingerprint] = my_cert.fingerprint end pson_hash.to_pson(*args) 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 puts detail.backtrace if Puppet[:trace] Puppet.err "Could not request certificate: #{detail}" 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 puts detail.backtrace if Puppet[:trace] Puppet.err "Could not request certificate: #{detail}" end end end def state my_cert = Puppet::SSL::Certificate.indirection.find(name) if certificate_request return 'requested' end begin Puppet::SSL::CertificateAuthority.new.verify(my_cert) return 'signed' rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError return 'revoked' end end end require 'puppet/ssl/certificate_authority' diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index d08b5a551..8353f34dd 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -1,301 +1,312 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/defaults' require 'puppet/rails' describe "Puppet defaults" do include Puppet::Util::Execution after { Puppet.settings.clear } describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end describe "when setting the :certname" do it "should fail if the certname is not downcased" do lambda { Puppet.settings[:certname] = "Host.Domain.Com" }.should raise_error(ArgumentError) end end describe "when setting :node_name_value" do it "should default to the value of :certname" do Puppet.settings[:certname] = 'blargle' Puppet.settings[:node_name_value].should == 'blargle' end end describe "when setting the :node_name_fact" do it "should fail when also setting :node_name_value" do lambda do Puppet.settings[:node_name_value] = "some value" Puppet.settings[:node_name_fact] = "some_fact" end.should raise_error("Cannot specify both the node_name_value and node_name_fact settings") end it "should not fail when using the default for :node_name_value" do lambda do Puppet.settings[:node_name_fact] = "some_fact" end.should_not raise_error end end + describe "when :certdnsnames is set" do + it "should not fail" do + expect { Puppet[:certdnsnames] = 'fred:wilma' }.not_to raise_error + end + + it "should warn the value is ignored" do + Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } + Puppet[:certdnsnames] = 'fred:wilma' + end + end + describe "when configuring the :crl" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) Puppet.settings[:cacrl] = 'false' end end describe "when setting the :catalog_format" do it "should log a deprecation notice" do Puppet.expects(:warning) Puppet.settings[:catalog_format] = 'marshal' end it "should copy the value to :preferred_serialization_format" do Puppet.settings[:catalog_format] = 'marshal' Puppet.settings[:preferred_serialization_format].should == 'marshal' end end it "should have a clientyamldir setting" do Puppet.settings[:clientyamldir].should_not be_nil end it "should have different values for the yamldir and clientyamldir" do Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir] end it "should have a client_datadir setting" do Puppet.settings[:client_datadir].should_not be_nil end it "should have different values for the server_datadir and client_datadir" do Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir] end # See #1232 it "should not specify a user or group for the clientyamldir" do Puppet.settings.setting(:clientyamldir).owner.should be_nil Puppet.settings.setting(:clientyamldir).group.should be_nil end it "should use the service user and group for the yamldir" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] end # See #1232 it "should not specify a user or group for the rundir" do Puppet.settings.setting(:rundir).owner.should be_nil Puppet.settings.setting(:rundir).group.should be_nil end it "should specify that the host private key should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user] end it "should specify that the host certificate should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user] end it "should use a bind address of ''" do Puppet.settings.clear Puppet.settings[:bindaddress].should == "" end [:factdest].each do |setting| it "should force the :factdest to be a directory" do Puppet.settings[setting].should =~ /\/$/ end end [:modulepath, :factpath].each do |setting| it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::Setting) end end it "should add /usr/sbin and /sbin to the path if they're not there" do withenv("PATH" => "/usr/bin:/usr/local/bin") do Puppet.settings[:path] = "none" # this causes it to ignore the setting ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin") ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin") end end it "should default to pson for the preferred serialization format" do Puppet.settings.value(:preferred_serialization_format).should == "pson" end describe "when enabling storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set the Catalog cache class to :store_configs" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should not set the Catalog cache class to :store_configs if asynchronous storeconfigs is enabled" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs).never Puppet.settings.expects(:value).with(:async_storeconfigs).returns true Puppet.settings[:storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling asynchronous storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:async_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end it "should set the Catalog cache class to :queue" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue) Puppet.settings[:async_storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling thin storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:thin_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end end it "should have a setting for determining the configuration version and should default to an empty string" do Puppet.settings[:config_version].should == "" end describe "when enabling reports" do it "should use the default server value when report server is unspecified" do Puppet.settings[:server] = "server" Puppet.settings[:report_server].should == "server" end it "should use the default masterport value when report port is unspecified" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port].should == "1234" end it "should set report_server when reportserver is set" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server].should == "reportserver" end it "should use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" Puppet.settings[:report_port].should == "5678" end it "should prefer report_server over reportserver" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server] = "report_server" Puppet.settings[:report_server].should == "report_server" end end it "should have a :caname setting that defaults to the cert name" do Puppet.settings[:certname] = "foo" Puppet.settings[:ca_name].should == "Puppet CA: foo" end it "should have a 'prerun_command' that defaults to the empty string" do Puppet.settings[:prerun_command].should == "" end it "should have a 'postrun_command' that defaults to the empty string" do Puppet.settings[:postrun_command].should == "" end it "should have a 'certificate_revocation' setting that defaults to true" do Puppet.settings[:certificate_revocation].should be_true end it "should have an http_compression setting that defaults to false" do Puppet.settings[:http_compression].should be_false end describe "reportdir" do subject { Puppet.settings[:reportdir] } it { should == "#{Puppet[:vardir]}/reports" } end describe "reporturl" do subject { Puppet.settings[:reporturl] } it { should == "http://localhost:3000/reports/upload" } end describe "when configuring color" do it "should default to ansi", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:color].should == 'ansi' end it "should default to false", :if => Puppet.features.microsoft_windows? do Puppet.settings[:color].should == 'false' end end describe "daemonize" do it "should default to true", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:daemonize].should == true end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should default to false" do Puppet.settings[:daemonize].should == false end it "should raise an error if set to true" do lambda { Puppet.settings[:daemonize] = true }.should raise_error(/Cannot daemonize on Windows/) end end end end diff --git a/spec/unit/ssl/certificate_factory_spec.rb b/spec/unit/ssl/certificate_factory_spec.rb index 070291fd4..ce821c3b2 100755 --- a/spec/unit/ssl/certificate_factory_spec.rb +++ b/spec/unit/ssl/certificate_factory_spec.rb @@ -1,127 +1,127 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/certificate_factory' describe Puppet::SSL::CertificateFactory do let :serial do OpenSSL::BN.new('12') end let :name do "example.local" end let :x509_name do OpenSSL::X509::Name.new([['CN', name]]) end let :key do Puppet::SSL::Key.new(name).generate end let :csr do csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(key) csr end let :issuer do cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.new([["CN", 'issuer.local']]) cert end describe "when generating the certificate" do it "should return a new X509 certificate" do subject.build(:server, csr, issuer, serial).should_not == subject.build(:server, csr, issuer, serial) end it "should set the certificate's version to 2" do subject.build(:server, csr, issuer, serial).version.should == 2 end it "should set the certificate's subject to the CSR's subject" do cert = subject.build(:server, csr, issuer, serial) cert.subject.should eql x509_name end it "should set the certificate's issuer to the Issuer's subject" do cert = subject.build(:server, csr, issuer, serial) cert.issuer.should eql issuer.subject end it "should set the certificate's public key to the CSR's public key" do cert = subject.build(:server, csr, issuer, serial) cert.public_key.should be_public cert.public_key.to_s.should == csr.content.public_key.to_s end it "should set the certificate's serial number to the provided serial number" do cert = subject.build(:server, csr, issuer, serial) cert.serial.should == serial end it "should have 24 hours grace on the start of the cert" do cert = subject.build(:server, csr, issuer, serial) cert.not_before.should be_within(1).of(Time.now - 24*60*60) end it "should set the default TTL of the certificate" do ttl = Puppet::SSL::CertificateFactory.ttl cert = subject.build(:server, csr, issuer, serial) cert.not_after.should be_within(1).of(Time.now + ttl) end it "should respect a custom TTL for the CA" do Puppet[:ca_ttl] = 12 cert = subject.build(:server, csr, issuer, serial) cert.not_after.should be_within(1).of(Time.now + 12) end it "should build extensions for the certificate" do cert = subject.build(:server, csr, issuer, serial) exts = cert.extensions.map {|x| x.to_h }.group_by {|x| x["oid"] } exts["nsComment"].should == [{ "oid" => "nsComment", "value" => "Puppet Ruby/OpenSSL Internal Certificate", "critical" => false }] end # See #2848 for why we are doing this: we need to make sure that # subjectAltName is set if the CSR has it, but *not* if it is set when the # certificate is built! - it "should not add subjectAltNames from certdnsnames" do - Puppet[:certdnsnames] = 'one:two' + it "should not add subjectAltNames from master_dns_alt_names" do + Puppet[:master_dns_alt_names] = 'one:two' # Verify the CSR still has no extReq, just in case... csr.request_extensions.should == [] cert = subject.build(:server, csr, issuer, serial) cert.extensions.find {|x| x.oid == 'subjectAltName' }.should be_nil end it "should add subjectAltName when the CSR requests them" do - Puppet[:certdnsnames] = '' + Puppet[:master_dns_alt_names] = '' expect = %w{one two} + [name] csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(key, :subject_alt_name => expect.join(':')) csr.request_extensions.should_not be_nil csr.subject_alt_names.should =~ expect.map{|x| "DNS:#{x}"} cert = subject.build(:server, csr, issuer, serial) san = cert.extensions.find {|x| x.oid == 'subjectAltName' } san.should_not be_nil expect.each do |name| san.value.should =~ /DNS:#{name}\b/i end end # Can't check the CA here, since that requires way more infrastructure # that I want to build up at this time. We can verify the critical # values, though, which are non-CA certs. --daniel 2011-10-11 { :ca => 'CA:TRUE', :terminalsubca => ['CA:TRUE', 'pathlen:0'], :server => 'CA:FALSE', :ocsp => 'CA:FALSE', :client => 'CA:FALSE', }.each do |name, value| it "should set basicConstraints for #{name} #{value.inspect}" do cert = subject.build(name, csr, issuer, serial) bc = cert.extensions.find {|x| x.oid == 'basicConstraints' } bc.should be bc.value.split(/\s*,\s*/).should =~ Array(value) end end end end diff --git a/spec/unit/ssl/certificate_request_spec.rb b/spec/unit/ssl/certificate_request_spec.rb index ce6597a44..357049c81 100755 --- a/spec/unit/ssl/certificate_request_spec.rb +++ b/spec/unit/ssl/certificate_request_spec.rb @@ -1,277 +1,277 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/certificate_request' require 'puppet/ssl/key' describe Puppet::SSL::CertificateRequest do before do @class = Puppet::SSL::CertificateRequest end it "should be extended with the Indirector module" do @class.singleton_class.should be_include(Puppet::Indirector) end it "should indirect certificate_request" do @class.indirection.name.should == :certificate_request end it "should use any provided name as its name" do @class.new("myname").name.should == "myname" end it "should only support the text format" do @class.supported_formats.should == [:s] end describe "when converting from a string" do it "should create a CSR instance with its name set to the CSR subject and its content set to the extracted CSR" do csr = stub 'csr', :subject => "/CN=Foo.madstop.com" OpenSSL::X509::Request.expects(:new).with("my csr").returns(csr) mycsr = stub 'sslcsr' mycsr.expects(:content=).with(csr) @class.expects(:new).with("foo.madstop.com").returns mycsr @class.from_s("my csr") end end describe "when managing instances" do before do @request = @class.new("myname") end it "should have a name attribute" do @request.name.should == "myname" end it "should downcase its name" do @class.new("MyName").name.should == "myname" end it "should have a content attribute" do @request.should respond_to(:content) end it "should be able to read requests from disk" do path = "/my/path" File.expects(:read).with(path).returns("my request") request = mock 'request' OpenSSL::X509::Request.expects(:new).with("my request").returns(request) @request.read(path).should equal(request) @request.content.should equal(request) end it "should return an empty string when converted to a string with no request" do @request.to_s.should == "" end it "should convert the request to pem format when converted to a string" do request = mock 'request', :to_pem => "pem" @request.content = request @request.to_s.should == "pem" end it "should have a :to_text method that it delegates to the actual key" do real_request = mock 'request' real_request.expects(:to_text).returns "requesttext" @request.content = real_request @request.to_text.should == "requesttext" end end describe "when generating" do before do @instance = @class.new("myname") key = Puppet::SSL::Key.new("myname") @key = key.generate @request = OpenSSL::X509::Request.new OpenSSL::X509::Request.expects(:new).returns(@request) @request.stubs(:verify).returns(true) end it "should use the content of the provided key if the key is a Puppet::SSL::Key instance" do key = Puppet::SSL::Key.new("test") key.expects(:content).returns @key @request.expects(:sign).with{ |key, digest| key == @key } @instance.generate(key) end it "should log that it is creating a new certificate request" do Puppet.expects(:info).twice @instance.generate(@key) end it "should set the subject to [CN, name]" do subject = mock 'subject' OpenSSL::X509::Name.expects(:new).with([["CN", @instance.name]]).returns(subject) @request.expects(:subject=).with(subject) @instance.generate(@key) end it "should set the CN to the CSR name when the CSR is not for a CA" do subject = mock 'subject' OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == @instance.name }.returns(subject) @request.expects(:subject=).with(subject) @instance.generate(@key) end it "should set the CN to the :ca_name setting when the CSR is for a CA" do subject = mock 'subject' Puppet[:ca_name] = "mycertname" OpenSSL::X509::Name.expects(:new).with { |subject| subject[0][1] == "mycertname" }.returns(subject) @request.expects(:subject=).with(subject) Puppet::SSL::CertificateRequest.new(Puppet::SSL::CA_NAME).generate(@key) end it "should set the version to 0" do @request.expects(:version=).with(0) @instance.generate(@key) end it "should set the public key to the provided key's public key" do # Yay, the private key extracts a new key each time. pubkey = @key.public_key @key.stubs(:public_key).returns pubkey @request.expects(:public_key=).with(@key.public_key) @instance.generate(@key) end - context "without subjectAltName / certdnsnames" do + context "without subjectAltName / master_dns_alt_names" do before :each do - Puppet[:certdnsnames] = "" + Puppet[:master_dns_alt_names] = "" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do @request.expects(:add_attribute).never @request.expects(:attributes=).never @instance.generate(@key) end it "should return no subjectAltNames" do @instance.generate(@key) @instance.subject_alt_names.should be_nil end end end - context "with certdnsnames" do + context "with master_dns_alt_names" do before :each do - Puppet[:certdnsnames] = "one:two:three" + Puppet[:master_dns_alt_names] = "one:two:three" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do @request.expects(:add_attribute).never @request.expects(:attributes=).never @instance.generate(@key) end it "should return no subjectAltNames" do @instance.generate(@key) @instance.subject_alt_names.should be_nil end end end context "with subjectAltName to generate request" do before :each do - Puppet[:certdnsnames] = "" + Puppet[:master_dns_alt_names] = "" end it "should add an extreq attribute" do @request.expects(:add_attribute).with do |arg| arg.value.value.all? do |x| x.value.all? do |y| y.value[0].value == "subjectAltName" end end end @instance.generate(@key, :subject_alt_name => 'one:two') end it "should return the subjectAltName values" do @instance.generate(@key, :subject_alt_name => 'one:two') @instance.subject_alt_names.should =~ ["DNS:myname", "DNS:one", "DNS:two"] end end it "should sign the csr with the provided key and a digest" do digest = mock 'digest' OpenSSL::Digest::MD5.expects(:new).returns(digest) @request.expects(:sign).with(@key, digest) @instance.generate(@key) end it "should verify the generated request using the public key" do # Stupid keys don't have a competent == method. @request.expects(:verify).with { |public_key| public_key.to_s == @key.public_key.to_s }.returns true @instance.generate(@key) end it "should fail if verification fails" do @request.expects(:verify).returns false lambda { @instance.generate(@key) }.should raise_error(Puppet::Error) end it "should fingerprint the request" do @instance.expects(:fingerprint) @instance.generate(@key) end it "should display the fingerprint" do Puppet.stubs(:info) @instance.stubs(:fingerprint).returns("FINGERPRINT") Puppet.expects(:info).with { |s| s =~ /FINGERPRINT/ } @instance.generate(@key) end it "should return the generated request" do @instance.generate(@key).should equal(@request) end it "should set its content to the generated request" do @instance.generate(@key) @instance.content.should equal(@request) end end describe "when a CSR is saved" do describe "and a CA is available" do it "should save the CSR and trigger autosigning" do ca = mock 'ca', :autosign Puppet::SSL::CertificateAuthority.expects(:instance).returns ca csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end describe "and a CA is not available" do it "should save the CSR" do Puppet::SSL::CertificateAuthority.expects(:instance).returns nil csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end end end diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index 083a281dd..2606feea5 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -1,863 +1,864 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/host' require 'puppet/sslcertificates' require 'puppet/sslcertificates/ca' # REMIND: Fails on windows because there is no user provider yet describe Puppet::SSL::Host, :fails_on_windows => true do include PuppetSpec::Files before do Puppet::SSL::Host.indirection.terminus_class = :file # Get a safe temporary file dir = tmpdir("ssl_host_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings.use :main, :ssl @host = Puppet::SSL::Host.new("myname") end after do # Cleaned out any cached localhost instance. Puppet::SSL::Host.reset Puppet::SSL::Host.ca_location = :none end it "should use any provided name as its name" do @host.name.should == "myname" end it "should retrieve its public key from its private key" do realkey = mock 'realkey' key = stub 'key', :content => realkey Puppet::SSL::Key.indirection.stubs(:find).returns(key) pubkey = mock 'public_key' realkey.expects(:public_key).returns pubkey @host.public_key.should equal(pubkey) end it "should default to being a non-ca host" do @host.ca?.should be_false end it "should be a ca host if its name matches the CA_NAME" do Puppet::SSL::Host.stubs(:ca_name).returns "yayca" Puppet::SSL::Host.new("yayca").should be_ca end it "should have a method for determining the CA location" do Puppet::SSL::Host.should respond_to(:ca_location) end it "should have a method for specifying the CA location" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for retrieving the default ssl host" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for producing an instance to manage the local host's keys" do Puppet::SSL::Host.should respond_to(:localhost) end it "should allow to reset localhost" do previous_host = Puppet::SSL::Host.localhost Puppet::SSL::Host.reset Puppet::SSL::Host.localhost.should_not == previous_host end it "should generate the certificate for the localhost instance if no certificate is available" do host = stub 'host', :key => nil Puppet::SSL::Host.expects(:new).returns host host.expects(:certificate).returns nil host.expects(:generate) Puppet::SSL::Host.localhost.should equal(host) end context "#this_csr_is_for_a_local_master" do it "should treat hostnames other than :certname as non-master" do Puppet[:certname] = 'fred.local' Puppet::SSL::Host.new('barney.local').this_csr_is_for_a_local_master.should be_false end it "should be true if we are a CA" do Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:master]) Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet[:ca] = true Puppet::SSL::Host.new(Puppet[:certname]). this_csr_is_for_a_local_master.should be_true end it "should be true if we are in master mode, but not a CA" do Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:master]) Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) Puppet[:ca] = false Puppet::SSL::Host.new(Puppet[:certname]). this_csr_is_for_a_local_master.should be_true end it "should be false otherwise" do Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:user]) Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) Puppet[:ca] = false Puppet::SSL::Host.new(Puppet[:certname]). this_csr_is_for_a_local_master.should be_false end end - context "with certdnsnames" do + context "with master_dns_alt_names" do before :each do - Puppet[:certdnsnames] = 'one:two' + Puppet[:master_dns_alt_names] = 'one:two' @key = stub('key content') key = stub('key', :generate => true, :save => true, :content => @key) Puppet::SSL::Key.stubs(:new).returns key @cr = stub('certificate request', :save => true) Puppet::SSL::CertificateRequest.stubs(:new).returns @cr end it "should not include subjectAltName if not a CA" do @cr.expects(:generate).with(@key, {}) Puppet[:ca] = false Puppet::SSL::Host.localhost end it "should include subjectAltName if I am a CA" do - @cr.expects(:generate).with(@key, { :subject_alt_name => Puppet[:certdnsnames] }) + @cr.expects(:generate). + with(@key, { :subject_alt_name => Puppet[:master_dns_alt_names] }) Puppet[:ca] = true Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:master]) Puppet::SSL::CertificateAuthority.stubs(:instance).returns stub('ca', :sign => nil) Puppet::SSL::Host.localhost end end it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host host.expects(:key) Puppet::SSL::Host.localhost end it "should cache the localhost instance" do host = stub 'host', :certificate => "eh", :key => 'foo' Puppet::SSL::Host.expects(:new).once.returns host Puppet::SSL::Host.localhost.should == Puppet::SSL::Host.localhost end it "should be able to verify its certificate matches its key" do Puppet::SSL::Host.new("foo").should respond_to(:certificate_matches_key?) end it "should consider the certificate invalid if it cannot find a key" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if it cannot find a certificate" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns mock("key") host.expects(:certificate).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if the SSL certificate's key verification fails" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns false host.should_not be_certificate_matches_key end it "should consider the certificate valid if the SSL certificate's key verification succeeds" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns true host.should be_certificate_matches_key end describe "when specifying the CA location" do it "should support the location ':local'" do lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error end it "should support the location ':remote'" do lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error end it "should support the location ':none'" do lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error end it "should support the location ':only'" do lambda { Puppet::SSL::Host.ca_location = :only }.should_not raise_error end it "should not support other modes" do lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) end describe "as 'local'" do before do Puppet::SSL::Host.ca_location = :local end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key and Host as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Host.indirection.terminus_class.should == :file end it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end end describe "as 'remote'" do before do Puppet::SSL::Host.ca_location = :remote end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file end it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do Puppet::SSL::Host.indirection.terminus_class.should == :rest Puppet::SSL::Certificate.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest end end describe "as 'only'" do before do Puppet::SSL::Host.ca_location = :only end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Key.indirection.terminus_class.should == :ca Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do Puppet::SSL::Certificate.indirection.cache_class.should be_nil Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil end it "should set the terminus class for Host to :file" do Puppet::SSL::Host.indirection.terminus_class.should == :file end end describe "as 'none'" do before do Puppet::SSL::Host.ca_location = :none end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Certificate.indirection.terminus_class.should == :file Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :file end it "should set the terminus class for Host to 'none'" do lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError) end end end it "should have a class method for destroying all files related to a given host" do Puppet::SSL::Host.should respond_to(:destroy) end describe "when destroying a host's SSL files" do before do Puppet::SSL::Key.indirection.stubs(:destroy).returns false Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false end it "should destroy its certificate, certificate request, and key" do Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") Puppet::SSL::Host.destroy("myhost") end it "should return true if any of the classes returned true" do Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true Puppet::SSL::Host.destroy("myhost").should be_true end it "should report that nothing was deleted if none of the classes returned true" do Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted" end end describe "when initializing" do it "should default its name to the :certname setting" do Puppet.settings.expects(:value).with(:certname).returns "myname" Puppet::SSL::Host.new.name.should == "myname" end it "should downcase a passed in name" do Puppet::SSL::Host.new("Host.Domain.Com").name.should == "host.domain.com" end it "should downcase the certname if it's used" do Puppet.settings.expects(:value).with(:certname).returns "Host.Domain.Com" Puppet::SSL::Host.new.name.should == "host.domain.com" end it "should indicate that it is a CA host if its name matches the ca_name constant" do Puppet::SSL::Host.stubs(:ca_name).returns "myca" Puppet::SSL::Host.new("myca").should be_ca end end describe "when managing its private key" do before do @realkey = "mykey" @key = Puppet::SSL::Key.new("mykey") @key.content = @realkey end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) @host.key.should be_nil end it "should find the key in the Key class and return the Puppet instance" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) @host.key.should equal(@key) end it "should be able to generate and save a new key" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) Puppet::SSL::Key.indirection.expects(:save) @host.generate_key.should be_true @host.key.should equal(@key) end it "should not retain keys that could not be saved" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.stubs(:generate) Puppet::SSL::Key.indirection.expects(:save).raises "eh" lambda { @host.generate_key }.should raise_error @host.key.should be_nil end it "should return any previously found key without requerying" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once @host.key.should equal(@key) @host.key.should equal(@key) end end describe "when managing its certificate request" do before do @realrequest = "real request" @request = Puppet::SSL::CertificateRequest.new("myname") @request.content = @realrequest end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) @host.certificate_request.should be_nil end it "should find the request in the Key class and return it and return the Puppet SSL request" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request @host.certificate_request.should equal(@request) end it "should generate a new key when generating the cert request if no key exists" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.expects(:key).times(2).returns(nil).then.returns(key) @host.expects(:generate_key).returns(key) @request.stubs(:generate) Puppet::SSL::CertificateRequest.indirection.stubs(:save) @host.generate_certificate_request end it "should be able to generate and save a new request using the private key" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.expects(:generate).with("mycontent", {}) Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) @host.generate_certificate_request.should be_true @host.certificate_request.should equal(@request) end it "should return any previously found request without requerying" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once @host.certificate_request.should equal(@request) @host.certificate_request.should equal(@request) end it "should not keep its certificate request in memory if the request cannot be saved" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.stubs(:generate) @request.stubs(:name).returns("myname") terminus = stub 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" lambda { @host.generate_certificate_request }.should raise_error @host.instance_eval { @certificate_request }.should be_nil end end describe "when managing its certificate" do before do @realcert = mock 'certificate' @cert = stub 'cert', :content => @realcert @host.stubs(:key).returns mock("key") @host.stubs(:certificate_matches_key?).returns true end it "should find the CA certificate if it does not have a certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert @host.certificate end it "should not find the CA certificate if it is the CA host" do @host.expects(:ca?).returns true Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).never @host.certificate end it "should return nil if it cannot find a CA certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns nil Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never @host.certificate.should be_nil end it "should find the key if it does not have one" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns mock("key") @host.certificate end it "should generate the key if one cannot be found" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns nil @host.expects(:generate_key) @host.certificate end it "should find the certificate in the Certificate class and return the Puppet certificate instance" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert @host.certificate.should equal(@cert) end it "should fail if the found certificate does not match the private key" do @host.expects(:certificate_matches_key?).returns false Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert lambda { @host.certificate }.should raise_error(Puppet::Error) end it "should return any previously found certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once @host.certificate.should equal(@cert) @host.certificate.should equal(@cert) end end it "should have a method for listing certificate hosts" do Puppet::SSL::Host.should respond_to(:search) end describe "when listing certificate hosts" do it "should default to listing all clients with any file types" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search end it "should be able to list only clients with a key" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Key end it "should be able to list only clients with a certificate" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Certificate end it "should be able to list only clients with a certificate request" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest end it "should return a Host instance created with the name of each found instance", :'fails_on_ruby_1.9.2' => true do key = stub 'key', :name => "key" cert = stub 'cert', :name => "cert" csr = stub 'csr', :name => "csr" Puppet::SSL::Key.indirection.expects(:search).returns [key] Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] returned = [] %w{key cert csr}.each do |name| result = mock(name) returned << result Puppet::SSL::Host.expects(:new).with(name).returns result end result = Puppet::SSL::Host.search returned.each do |r| result.should be_include(r) end end end it "should have a method for generating all necessary files" do Puppet::SSL::Host.new("me").should respond_to(:generate) end describe "when generating files" do before do @host = Puppet::SSL::Host.new("me") @host.stubs(:generate_key) @host.stubs(:generate_certificate_request) end it "should generate a key if one is not present" do @host.stubs(:key).returns nil @host.expects(:generate_key) @host.generate end it "should generate a certificate request if one is not present" do @host.expects(:certificate_request).returns nil @host.expects(:generate_certificate_request) @host.generate end describe "and it can create a certificate authority" do before do @ca = mock 'ca' Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca end it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil @ca.expects(:sign).with(@host.name, true) @host.generate end end describe "and it cannot create a certificate authority" do before do Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil end it "should seek its certificate" do @host.expects(:certificate) @host.generate end end end it "should have a method for creating an SSL store" do Puppet::SSL::Host.new("me").should respond_to(:ssl_store) end it "should always return the same store" do host = Puppet::SSL::Host.new("foo") store = mock 'store' store.stub_everything OpenSSL::X509::Store.expects(:new).returns store host.ssl_store.should equal(host.ssl_store) end describe "when creating an SSL store" do before do @host = Puppet::SSL::Host.new("me") @store = mock 'store' @store.stub_everything OpenSSL::X509::Store.stubs(:new).returns @store Puppet.settings.stubs(:value).with(:localcacert).returns "ssl_host_testing" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) end it "should accept a purpose" do @store.expects(:purpose=).with "my special purpose" @host.ssl_store("my special purpose") end it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY @host.ssl_store end it "should add the local CA cert file" do Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" @store.expects(:add_file).with "/ca/cert/file" @host.ssl_store end describe "and a CRL is available" do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl Puppet.settings.stubs(:value).with(:certificate_revocation).returns true end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @host.ssl_store end end end describe "when waiting for a cert" do before do @host = Puppet::SSL::Host.new("me") end it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate) @host.wait_for_cert(1) end it "should catch and log errors during CSR saving" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.stubs(:sleep) @host.wait_for_cert(1) end it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should exit after failures saving the CSR of waitforcert is disabled" do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" @host.expects(:generate) @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should catch and log exceptions during certificate retrieval" do @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") @host.stubs(:generate) @host.stubs(:sleep) Puppet.expects(:err) @host.wait_for_cert(1) end end describe "when handling PSON", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") Puppet::SSLCertificates::CA.new.mkrootcert # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] @ca=Puppet::SSL::CertificateAuthority.new end describe "when converting to PSON" do it "should be able to identify a host with an unsigned certificate request" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request pson_hash = { "fingerprint" => host.certificate_request.fingerprint, "desired_state" => 'requested', "name" => host.name } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a signed certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'signed', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a revoked certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) @ca.revoke(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'revoked', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end end describe "when converting from PSON" do it "should return a Puppet::SSL::Host object with the specified desired state" do host = Puppet::SSL::Host.new("bazinga") host.desired_state="signed" pson_hash = { "name" => host.name, "desired_state" => host.desired_state, } generated_host = Puppet::SSL::Host.from_pson(pson_hash) generated_host.desired_state.should == host.desired_state generated_host.name.should == host.name end end end end diff --git a/test/lib/puppettest/exetest.rb b/test/lib/puppettest/exetest.rb index 5a2248dc9..ed0118a1c 100644 --- a/test/lib/puppettest/exetest.rb +++ b/test/lib/puppettest/exetest.rb @@ -1,125 +1,125 @@ require 'puppettest/servertest' module PuppetTest::ExeTest include PuppetTest::ServerTest def setup super setbindir setlibdir end def bindir File.join(basedir, "bin") end def sbindir File.join(basedir, "sbin") end def setbindir ENV["PATH"] = [bindir, ENV["PATH"]].join(":") unless ENV["PATH"].split(":").include?(bindir) ENV["PATH"] = [sbindir, ENV["PATH"]].join(":") unless ENV["PATH"].split(":").include?(sbindir) end def setlibdir ENV["RUBYLIB"] = $LOAD_PATH.find_all { |dir| dir =~ /puppet/ or dir =~ /\.\./ }.join(":") end # Run a ruby command. This explicitly uses ruby to run stuff, since we # don't necessarily know where our ruby binary is, dernit. # Currently unused, because I couldn't get it to work. def rundaemon(*cmd) @ruby ||= %x{which ruby}.chomp cmd = cmd.unshift(@ruby).join(" ") out = nil Dir.chdir(bindir) { out = %x{#{@ruby} #{cmd}} } out end def startmasterd(args = "") output = nil manifest = mktestmanifest args += " --manifest #{manifest}" args += " --confdir #{Puppet[:confdir]}" args += " --rundir #{File.join(Puppet[:vardir], "run")}" args += " --vardir #{Puppet[:vardir]}" - args += " --certdnsnames #{Puppet[:certdnsnames]}" + args += " --master_dns_alt_names #{Puppet[:master_dns_alt_names]}" args += " --masterport #{@@port}" args += " --user #{Puppet::Util::SUIDManager.uid}" args += " --group #{Puppet::Util::SUIDManager.gid}" args += " --autosign true" #if Puppet[:debug] # args += " --debug" #end cmd = "puppetmasterd #{args}" assert_nothing_raised { output = %x{#{cmd}}.chomp } assert_equal("", output, "Puppetmasterd produced output #{output}") assert($CHILD_STATUS == 0, "Puppetmasterd exit status was #{$CHILD_STATUS}") sleep(1) cleanup do stopmasterd sleep(1) end manifest end def stopmasterd(running = true) ps = Facter["ps"].value || "ps -ef" pidfile = File.join(Puppet[:vardir], "run", "puppetmasterd.pid") pid = nil if FileTest.exists?(pidfile) pid = File.read(pidfile).chomp.to_i File.unlink(pidfile) end return unless running if running or pid runningpid = nil %x{#{ps}}.chomp.split(/\n/).each { |line| if line =~ /ruby.+puppetmasterd/ next if line =~ /\.rb/ # skip the test script itself next if line =~ /^puppet/ # skip masters running as 'puppet' ary = line.sub(/^\s+/, '').split(/\s+/) pid = ary[1].to_i end } end # we default to mandating that it's running, but teardown # doesn't require that if pid if pid == $PID raise Puppet::Error, "Tried to kill own pid" end begin Process.kill(:INT, pid) rescue # ignore it end end end def teardown stopmasterd(false) super end end diff --git a/test/network/server/webrick.rb b/test/network/server/webrick.rb index e1fd68921..4d7bf32f4 100755 --- a/test/network/server/webrick.rb +++ b/test/network/server/webrick.rb @@ -1,111 +1,111 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') require 'puppettest' require 'puppet/network/http_server/webrick' require 'mocha' class TestWebrickServer < Test::Unit::TestCase include PuppetTest::ServerTest def setup Puppet::Util::SUIDManager.stubs(:asuser).yields Puppet::SSL::Host.instance_variable_set(:@localhost, nil) super end # Make sure we can create a server, and that it knows how to create its # certs by default. def test_basics server = nil assert_raise(Puppet::Error, "server succeeded with no cert") do server = Puppet::Network::HTTPServer::WEBrick.new( :Port => @@port, :Handlers => { :Status => nil } ) end assert_nothing_raised("Could not create simple server") do server = Puppet::Network::HTTPServer::WEBrick.new( :Port => @@port, :Handlers => { :CA => {}, # so that certs autogenerate :Status => nil } ) end assert(server, "did not create server") assert(server.cert, "did not retrieve cert") end # test that we can connect to the server # we have to use fork here, because we apparently can't use threads # to talk to other threads def test_connect_with_fork Puppet[:autosign] = true serverpid, server = mk_status_server # create a status client, and verify it can talk client = mk_status_client assert(client.cert, "did not get cert for client") retval = nil assert_nothing_raised("Could not connect to server") { retval = client.status } assert_equal(1, retval) end def mk_status_client client = nil assert_nothing_raised { client = Puppet::Network::Client.status.new( :Server => "localhost", :Port => @@port ) } client end def mk_status_server server = nil - Puppet[:certdnsnames] = "localhost" + Puppet[:master_dns_alt_names] = "localhost" assert_nothing_raised { server = Puppet::Network::HTTPServer::WEBrick.new( :Port => @@port, :Handlers => { :CA => {}, # so that certs autogenerate :Status => nil } ) } pid = fork { Puppet.run_mode.stubs(:master?).returns true assert_nothing_raised { trap(:INT) { server.shutdown } server.start } } @@tmppids << pid [pid, server] end def kill_and_wait(pid, file) %x{kill -INT #{pid} 2>/dev/null} count = 0 while count < 30 && File::exist?(file) count += 1 sleep(1) end assert(count < 30, "Killing server #{pid} failed") end end diff --git a/test/ral/type/filesources.rb b/test/ral/type/filesources.rb index f39d53907..be8c40d35 100755 --- a/test/ral/type/filesources.rb +++ b/test/ral/type/filesources.rb @@ -1,506 +1,501 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') require 'puppettest' require 'puppettest/support/utils' require 'cgi' require 'fileutils' require 'mocha' class TestFileSources < Test::Unit::TestCase include PuppetTest::Support::Utils include PuppetTest::FileTesting def setup super if defined?(@port) @port += 1 else @port = 12345 end @file = Puppet::Type.type(:file) Puppet[:filetimeout] = -1 Puppet::Util::SUIDManager.stubs(:asuser).yields Facter.stubs(:to_hash).returns({}) end def teardown super end def use_storage initstorage rescue system("rm -rf #{Puppet[:statefile]}") end def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end # Make a simple recursive tree. def mk_sourcetree source = tempfile sourcefile = File.join(source, "file") Dir.mkdir source File.open(sourcefile, "w") { |f| f.puts "yay" } dest = tempfile destfile = File.join(dest, "file") return source, dest, sourcefile, destfile end def recursive_source_test(fromdir, todir) initstorage tofile = nil trans = nil tofile = Puppet::Type.type(:file).new( :path => todir, :recurse => true, :backup => false, :source => fromdir ) catalog = mk_catalog(tofile) catalog.apply assert(FileTest.exists?(todir), "Created dir #{todir} does not exist") end def run_complex_sources(networked = false) path = tempfile # first create the source directory FileUtils.mkdir_p path # okay, let's create a directory structure fromdir = File.join(path,"fromdir") Dir.mkdir(fromdir) FileUtils.cd(fromdir) { File.open("one", "w") { |f| f.puts "onefile"} File.open("two", "w") { |f| f.puts "twofile"} } todir = File.join(path, "todir") source = fromdir source = "puppet://localhost/#{networked}#{fromdir}" if networked recursive_source_test(source, todir) [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")] end def test_complex_sources_twice fromdir, todir, one, two = run_complex_sources assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) # Now remove the whole tree and try it again. [one, two].each do |f| File.unlink(f) end Dir.rmdir(todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) end def test_sources_with_deleted_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # then delete a file File.unlink(two) # and run recursive_source_test(fromdir, todir) assert(FileTest.exists?(two), "Deleted file was not recopied") # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_readonly_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) File.chmod(0600, one) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) # Now try it with the directory being read-only File.chmod(0111, todir) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_modified_dest_files fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # Modify a dest file File.open(two, "w") { |f| f.puts "something else" } recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_added_destfiles fromdir, todir = run_complex_sources assert(FileTest.exists?(todir)) # and finally, add some new files add_random_files(todir) recursive_source_test(fromdir, todir) fromtree = file_list(fromdir) totree = file_list(todir) assert(fromtree != totree, "Trees are incorrectly equal") # then remove our new files FileUtils.cd(todir) { %x{find . 2>/dev/null}.chomp.split(/\n/).each { |file| if file =~ /file[0-9]+/ FileUtils.rm_rf(file) end } } # and make sure they're still equal assert_trees_equal(fromdir,todir) end # Make sure added files get correctly caught during recursion def test_RecursionWithAddedFiles basedir = tempfile Dir.mkdir(basedir) @@tmpfiles << basedir file1 = File.join(basedir, "file1") file2 = File.join(basedir, "file2") subdir1 = File.join(basedir, "subdir1") file3 = File.join(subdir1, "file") File.open(file1, "w") { |f| f.puts "yay" } rootobj = nil assert_nothing_raised { rootobj = Puppet::Type.type(:file).new( :name => basedir, :recurse => true, :check => %w{type owner}, :mode => 0755 ) } assert_apply(rootobj) assert_equal(0755, filemode(file1)) File.open(file2, "w") { |f| f.puts "rah" } assert_apply(rootobj) assert_equal(0755, filemode(file2)) Dir.mkdir(subdir1) File.open(file3, "w") { |f| f.puts "foo" } assert_apply(rootobj) assert_equal(0755, filemode(file3)) end def mkfileserverconf(mounts) file = tempfile File.open(file, "w") { |f| mounts.each { |path, name| f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n" } } @@tmpfiles << file file end def test_unmountedNetworkSources server = nil mounts = { "/" => "root", "/noexistokay" => "noexist" } fileserverconf = mkfileserverconf(mounts) Puppet[:autosign] = true Puppet[:masterport] = @port - Puppet[:certdnsnames] = "localhost" + Puppet[:master_dns_alt_names] = "localhost" serverpid = nil assert_nothing_raised("Could not start on port #{@port}") { - - server = Puppet::Network::HTTPServer::WEBrick.new( - - :Port => @port, - - :Handlers => { - :CA => {}, # so that certs autogenerate - :FileServer => { - :Config => fileserverconf - } - } - ) - + server = Puppet::Network::HTTPServer::WEBrick. + new(:Port => @port, + :Handlers => { + :CA => {}, # so that certs autogenerate + :FileServer => { + :Config => fileserverconf + } + }) } serverpid = fork { assert_nothing_raised { #trap(:INT) { server.shutdown; Kernel.exit! } trap(:INT) { server.shutdown } server.start } } @@tmppids << serverpid sleep(1) name = File.join(tmpdir, "nosourcefile") file = Puppet::Type.type(:file).new( :source => "puppet://localhost/noexist/file", :name => name ) assert_raise Puppet::Error do file.retrieve end comp = mk_catalog(file) comp.apply assert(!FileTest.exists?(name), "File with no source exists anyway") end def test_sourcepaths files = [] 3.times { files << tempfile } to = tempfile File.open(files[-1], "w") { |f| f.puts "yee-haw" } file = nil assert_nothing_raised { file = Puppet::Type.type(:file).new( :name => to, :source => files ) } comp = mk_catalog(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") txt = nil File.open(to) { |f| txt = f.read.chomp } assert_equal("yee-haw", txt, "Contents do not match") end # Make sure that source-copying updates the checksum on the same run def test_sourcebeatsensure source = tempfile dest = tempfile File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet::Type.type(:file).new( :name => dest, :ensure => "file", :source => source ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end def test_sourcewithlinks source = tempfile link = tempfile dest = tempfile File.open(source, "w") { |f| f.puts "yay" } File.symlink(source, link) file = Puppet::Type.type(:file).new(:name => dest, :source => link) catalog = mk_catalog(file) # Default to managing links catalog.apply assert(FileTest.symlink?(dest), "Did not create link") # Now follow the links file[:links] = :follow catalog.apply assert(FileTest.file?(dest), "Destination is not a file") end # Make sure files aren't replaced when replace is false, but otherwise # are. def test_replace dest = tempfile file = Puppet::Type.newfile( :path => dest, :content => "foobar", :recurse => true ) assert_apply(file) File.open(dest, "w") { |f| f.puts "yayness" } file[:replace] = false assert_apply(file) # Make sure it doesn't change. assert_equal("yayness\n", File.read(dest), "File got replaced when :replace was false") file[:replace] = true assert_apply(file) # Make sure it changes. assert_equal("foobar", File.read(dest), "File was not replaced when :replace was true") end def test_sourceselect dest = tempfile sources = [] 2.times { |i| i = i + 1 source = tempfile sources << source file = File.join(source, "file#{i}") Dir.mkdir(source) File.open(file, "w") { |f| f.print "yay" } } file1 = File.join(dest, "file1") file2 = File.join(dest, "file2") file3 = File.join(dest, "file3") # Now make different files with the same name in each source dir sources.each_with_index do |source, i| File.open(File.join(source, "file3"), "w") { |f| f.print i.to_s } end obj = Puppet::Type.newfile( :path => dest, :recurse => true, :source => sources) assert_equal(:first, obj[:sourceselect], "sourceselect has the wrong default") # First, make sure we default to just copying file1 assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(! FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") # Now reset sourceselect assert_nothing_raised do obj[:sourceselect] = :all end File.unlink(file1) File.unlink(file3) Puppet.err :yay assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") end def test_recursive_sourceselect dest = tempfile source1 = tempfile source2 = tempfile files = [] [source1, source2, File.join(source1, "subdir"), File.join(source2, "subdir")].each_with_index do |dir, i| Dir.mkdir(dir) # Make a single file in each directory file = File.join(dir, "file#{i}") File.open(file, "w") { |f| f.puts "yay#{i}"} # Now make a second one in each directory file = File.join(dir, "second-file#{i}") File.open(file, "w") { |f| f.puts "yaysecond-#{i}"} files << file end obj = Puppet::Type.newfile(:path => dest, :source => [source1, source2], :sourceselect => :all, :recurse => true) assert_apply(obj) ["file0", "file1", "second-file0", "second-file1", "subdir/file2", "subdir/second-file2", "subdir/file3", "subdir/second-file3"].each do |file| path = File.join(dest, file) assert(FileTest.exists?(path), "did not create #{file}") assert_equal("yay#{File.basename(file).sub("file", '')}\n", File.read(path), "file was not copied correctly") end end # #594 def test_purging_missing_remote_files source = tempfile dest = tempfile s1 = File.join(source, "file1") s2 = File.join(source, "file2") d1 = File.join(dest, "file1") d2 = File.join(dest, "file2") Dir.mkdir(source) [s1, s2].each { |name| File.open(name, "w") { |file| file.puts "something" } } # We have to add a second parameter, because that's the only way to expose the "bug". file = Puppet::Type.newfile(:path => dest, :source => source, :recurse => true, :purge => true, :mode => "755") assert_apply(file) assert(FileTest.exists?(d1), "File1 was not copied") assert(FileTest.exists?(d2), "File2 was not copied") File.unlink(s2) assert_apply(file) assert(FileTest.exists?(d1), "File1 was not kept") assert(! FileTest.exists?(d2), "File2 was not purged") end end