diff --git a/README.md b/README.md index aa42d50..8d2715b 100644 --- a/README.md +++ b/README.md @@ -1,637 +1,645 @@ [![Build Status](https://travis-ci.org/Icinga/puppet-icinga2.svg?branch=master)](https://travis-ci.org/Icinga/puppet-icinga2) # Icinga 2 Puppet Module ![Icinga Logo](https://www.icinga.com/wp-content/uploads/2014/06/icinga_logo.png) #### Table of Contents 1. [Overview](#overview) 2. [Module Description - What the module does and why it is useful](#module-description) 3. [Setup - The basics of getting started with icinga2](#setup) 4. [Usage - Configuration options and additional functionality](#usage) * [Installing Icinga](#installing-icinga) * [Clustering Icinga](#clustering-icinga) * [Config Objects](#config-objects) * [Reading objects from hiera](#Reading-objects-from-hiera) * [Apply Rules](#apply-rules) * [Custom configuration](#custom-configuration) 5. [How Configuration is parsed](#how-configuration-is-parsed) 6. [Reference](#reference) 7. [Release Notes](#release-notes) ## Overview Icinga 2 is a widely used open source monitoring software. This Puppet module helps with installing and managing configuration of Icinga 2 on multiple operating systems. +### What's new in version 3.2.0 + +Some parameters for secrets like passwords or tokens in features or objects now allow the datatype 'Sensetive'. +Strings set to constants or as custom variables can also use Sensitive. They are not parsed by the simple config +parser. When you're using hashes or arrays in constants or custom variables the whole data structure can be +secured by Sensitive. + + ### What's new in version 3.0.0 * The current version now uses the icinga :: repos class from the new `icinga` module for the configuration of repositories including EPEL on RedHat and Backports on Debian. (see https://github.com/icinga/puppet-icinga) * `manage_repos` will replace `manage_repo` in the future * `manage_packages` will replace `manage_package` in the future * Since Icinga v2.12.0 the fingerprint to validate certificates is a sha256 instead of a sha1. Both is supported now. ## Module Description This module installs and configures Icinga 2 on your Linux or Windows hosts. By default it uses packages provided by your distribution's repository or [Chocolatey] on Windows. The module can also be configured to use [packages.icinga.com] as the primary repository, which enables you to install Icinga 2 versions that are newer than the ones provided by your distribution's vendor. All features and objects available in Icinga 2 can be enabled and configured with this module. ## Setup ### What the Icinga 2 Puppet module supports * Installation of packages * Configuration of features * Configuration of objects (also apply rules) * Service * MySQL / PostgreSQL Database Schema Import * Repository Management * Certification Authority ### Dependencies This module supports: * [puppet] >= 4.10 < 8.0.0 And depends on: * [puppetlabs/stdlib] >= 5.0.0 < 8.0.0 * If Puppet 6 is used a stdlib 5.1 or higher is required, see https://github.com/Icinga/puppet-icinga2/issues/505 * [puppetlabs/concat] >= 2.1.0 < 8.0.0 * [icinga/icinga] >= 1.0.0 < 3.0.0 * needed if `manage_repos` is set to `true` ### Limitations The use of Icinga's own CA is recommended. If you still want to use the Puppet certificates, please note that Puppet 7 uses an intermediate CA by default and Icinga cannot handle its CA certificate, see [Icinga Issue](https://github.com/Icinga/icinga2/pull/8859). This module has been tested on: * Ruby >= 1.9 * Debian 8, 9, 10 * Ubuntu 16.04, 18.04, 20.04 * CentOS/RHEL 6, 7, 8 * Fedora 31 * FreeBSD 10, 11 * SLES 12, 15 * Windows Server 2016 Other operating systems or versions may work but have not been tested. ## Usage ### Installing Icinga The default class `icinga2` installs and configures a basic installation of Icinga 2. The features `checker`, `mainlog` and `notification` are enabled by default. By default, your distribution's packages are used to install Icinga 2. On Windows systems we use the [Chocolatey] package manager. Use the `manage_repos` parameter to configure repositories by default the official and stable [packages.icinga.com]. To configure your own repositories, or use the official testing or nightly snapshot stage, see https://github.com/icinga/puppet-icinga. ``` puppet class { '::icinga2': manage_repos => true, } ``` If you want to manage the version of Icinga 2, you have to disable the package management of this module and handle packages in your own Puppet code. The attribute `manage_repos` is disabled by default and you have to manage a repository within icinga in front of the package resource. You can combine this one with the section before about repositories. ``` puppet # class of extra module icinga/icinga include ::icinga::repos package { 'icinga2': ensure => latest, notify => Class['icinga2'], } class { '::icinga2': manage_packages => false, } ``` Note: Be careful with this option: Setting `manage_packages` to false means that this module will not install any package at all, including IDO packages! ### Clustering Icinga Icinga 2 can run in three different roles: * in a master zone which is on top of the hierarchy * in a satellite zone which is a child of a satellite or master zone * a standalone client node/zone which works as an agent connected to master and/or satellite zones To learn more about Icinga 2 Clustering, follow the official docs on [distributed monitoring]. The following examples show how these roles can be configured using this Puppet module. #### Master A Master zone has no parent and is usually also the place where you enable the IDO and notification features. A master sends configurations over the Icinga 2 protocol to satellites and/or clients. More detailed examples can be found in the [examples] directory. This example creates the configuration for a master that has one satellite connected. A global zone is created for templates, and all features of a typical master are enabled. ``` puppet class { '::icinga2': confd => false, constants => { 'ZoneName' => 'master', 'TicketSalt' => '5a3d695b8aef8f18452fc494593056a4', }, } class { '::icinga2::feature::api': pki => 'none', accept_commands => true, # when having multiple masters, you have to enable: accept_config => true, endpoints => { 'master.example.org' => {}, 'satellite.example.org' => { 'host' => '172.16.2.11' }, }, zones => { 'master' => { 'endpoints' => ['master.example.org'], }, 'dmz' => { 'endpoints' => ['satellite.example.org'], 'parent' => 'master', }, } } # to enable a CA on this instance you have to declare. Only one instance is allowed to be a CA: include ::icinga2::pki::ca icinga2::object::zone { 'global-templates': global => true, } ``` #### Satellite A satellite has a parent zone and one or multiple child zones. Satellites are usually created to distribute the monitoring load or to reach delimited zones in the network. A satellite either executes checks itself or delegates them to a client. The satellite has fewer features enabled, but executes checks similar to a master. It connects to a master zone, and to a satellite or client below in the hierarchy. As parent acts either the master zone, or another satellite zone. ``` puppet class { '::icinga2': confd => false, # setting dedicated feature list to disable notification features => ['checker','mainlog'], constants => { 'ZoneName' => 'dmz', }, } class { '::icinga2::feature::api': accept_config => true, accept_commands => true, ca_host => '172.16.1.11', ticket_salt => '5a3d695b8aef8f18452fc494593056a4', # to increase your security set fingerprint to validate the certificate of ca_host # fingerprint => 'D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:50:68:01:D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:99:3D:96:72:72', endpoints => { 'satellite.example.org' => {}, 'master.example.org' => { 'host' => '172.16.1.11', }, }, zones => { 'master' => { 'endpoints' => ['master.example.org'], }, 'dmz' => { 'endpoints' => ['satellite.example.org'], 'parent' => 'master', }, } } icinga2::object::zone { 'global-templates': global => true, } ``` #### Agent Icinga 2 runs as a client usually on each of your servers. It receives config or commands from a satellite or master zones and runs the checks that have to be executed locally. The client is connected to the satellite, which is the direct parent zone. ``` puppet class { '::icinga2': confd => false, features => ['mainlog'], } class { '::icinga2::feature::api': accept_config => true, accept_commands => true, ticket_salt => '5a3d695b8aef8f18452fc494593056a4', # to increase your security set fingerprint to validate the certificate of ca_host # fingerprint => 'D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:50:68:01:D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:99:3D:96:72:72', endpoints => { 'NodeName' => {}, 'satellite.example.org' => { 'host' => '172.16.2.11', }, }, zones => { 'ZoneName' => { 'endpoints' => ['NodeName'], 'parent' => 'dmz', }, 'dmz' => { 'endpoints' => ['satellite.example.org'], }, } } icinga2::object::zone { 'global-templates': global => true, } ``` The parameter `fingerprint` is optional and new since v2.1.0. It's used to validate the certificate of the CA host. You can get the fingerprint via `openssl x509 -noout -fingerprint -sha256 -inform pem -in master.crt` on the master host. (Icinga2 versions before 2.12.0 require '-sha1' as digest algorithm.) ### Config Objects With this module you can create almost every object that Icinga 2 knows about. When creating objects some parameters are required. This module sets the same requirements as Icinga 2 does. When creating an object you must set a target for the configuration. Here are some examples for some object types: #### Host ``` puppet icinga2::object::host { 'srv-web1.fqdn.com': display_name => 'srv-web1.fqdn.com', address => '127.0.0.1', address6 => '::1', check_command => 'hostalive', target => '/etc/icinga2/conf.d/srv-web1.fqdn.com.conf', } ``` #### Service ``` puppet icinga2::object::service { 'uptime': host_name => 'srv-web1.fqdn.com', display_name => 'Uptime', check_command => 'check_uptime', check_interval => '600m', groups => ['uptime', 'linux'], target => '/etc/icinga2/conf.d/uptime.conf', } ``` #### Hostgroup ``` puppet icinga2::object::hostgroup { 'monitoring-hosts': display_name => 'Linux Servers', groups => [ 'linux-servers' ], target => '/etc/icinga2/conf.d/groups2.conf', assign => [ 'host.vars.os == linux' ], } ``` ### Reading objects from hiera The following example shows how icinga2 objects can be read from a hiera datastore. See also examples/objects_from_hiera.pp. ``` class { 'icinga2': manage_repos => true, } $defaults = lookup('monitoring::defaults', undef, undef, {}) lookup('monitoring::objects').each |String $object_type, Hash $content| { $content.each |String $object_name, Hash $object_config| { ensure_resource( $object_type, $object_name, deep_merge($defaults[$object_type], $object_config)) } } ``` The datastore could be like: ``` --- monitoring::objects: 'icinga2::object::host': centos7.localdomain: address: 127.0.0.1 vars: os: Linux 'icinga2::object::service': ping4: check_command: ping4 apply: true assign: - host.address ssh: check_command: ssh apply: true assign: - host.address && host.vars.os == Linux monitoring::defaults: 'icinga2::object::host': import: - generic-host target: /etc/icinga2/conf.d/hosts.conf 'icinga2::object::service': import: - generic-service target: /etc/icinga2/conf.d/services.conf ``` ### Apply Rules Some objects can be applied to other objects. To create a simple apply rule you must set the `apply` parameter to `true`. If this parameter is set to a string, this string will be used to build an `apply for` loop. A service object always targets a host object. All other objects need to explicitly set an `apply_target` Apply a SSH service to all Linux hosts: ``` icinga2::object::service { 'SSH': target => '/etc/icinga2/conf.d/test.conf', apply => true, assign => [ 'host.vars.os == Linux' ], ignore => [ 'host.vars.os == Windows' ], display_name => 'Test Service', check_command => 'ssh', } ``` Apply notifications to services: ``` icinga2::object::notification { 'testnotification': target => '/etc/icinga2/conf.d/test.conf', apply => true, apply_target => 'Service', assign => [ 'host.vars.os == Linux' ], ignore => [ 'host.vars.os == Windows' ], user_groups => ['icingaadmins'] } ``` Assign all Linux hosts to a hostgroup: ``` icinga2::object::hostgroup { 'monitoring-hosts': display_name => 'Linux Servers', groups => [ 'linux-servers' ], target => '/etc/icinga2/conf.d/groups2.conf', assign => [ 'host.vars.os == linux' ], } ``` A loop to create HTTP services for all vHosts of a host object: ``` icinga2::object::service { 'HTTP': target => '/etc/icinga2/conf.d/http.conf', apply => 'http_vhost => config in host.vars_http_vhost', assign => [ 'host.vars.os == Linux' ], display_name => 'HTTP Service', check_command => 'http', } ``` ### Custom Configuration Sometimes it's necessary to cover very special configurations, that you cannot handle with this module. In this case you can use the `icinga2::config::file` tag on your file resource. The module collects all file resource types with this tag and triggers a reload of Icinga 2 on a file change. ``` include ::icinga2 file { '/etc/icinga2/conf.d/for-loop.conf': ensure => file, source => '...', tag => 'icinga2::config::file', } ``` ## How Configuration is parsed To generate a valid Icinga 2 configuration all object attributes are parsed. This simple parsing algorithm takes a decision for each attribute, whether part of the string is to be quoted or not, and how an array or dictionary is to be formatted. Parsing of a single attribute can be disabled by tagging it with -: at the front of the string. ``` attr => '-:"unparsed string with quotes"' ``` An array, a hash or a string can be assigned to an object attribute. True and false are also valid values. Hashes and arrays are created recursively, and all parts – such as single items of an array, keys and its values are parsed separately as strings. Strings are parsed in chunks, by splitting the original string into separate substrings at specific keywords (operators) such as `+`, `-`, `in`, `&&`, `||`, etc. **NOTICE**: This splitting only works for keywords that are surrounded by whitespace, e.g.: ``` attr => 'string1 + string2 - string3' ``` The algorithm will loop over the parameter and start by splitting it into 'string1' and 'string2 - string3'. 'string1' will be passed to the sub function 'value_types' and then the algorithm will continue parsing the rest of the string ('string2 - string3'), splitting it, passing it to value_types, etc. Brackets are parsed for expressions: ``` attr => '3 * (value1 - value2) / 2' ``` The parser also detects function calls and will parse all parameters separately. ``` attr => 'function(param1, param2, ...)' ``` True and false can be used as either booleans or strings. ``` attrs => true or attr => 'true' ``` In Icinga you can write your own lambda functions with {{ ... }}. For Puppet use: ``` attrs => '{{ ... }}' ``` The parser analyzes which parts of the string have to be quoted and which do not. As a general rule, all fragments are quoted except for the following: * Boolean: `true`, `false` * Numbers: `3` or `2.5` * Time Intervals: `3m` or `2.5h` (s = seconds, m = minutes, h = hours, d = days) * Functions: `{{ ... }}` or function `()` `{}` * All constants, which are declared in the constants parameter in main class `icinga2` * `NodeName` * Names of attributes that belong to the same type of object: * e.g. `name` and `check_command` for a host object * All attributes or variables (custom attributes) from the host, service or user contexts: * `host.name`, `service.check_command`, `user.groups`, ... Assignment with += and -=: Now it's possible to build an Icinga DSL code snippet like ``` vars += config ``` simply use a string with the prefix '+ ', e.g. ``` vars => '+ config', ``` The blank between + and the proper string 'config' is imported for the parser because numbers ``` attr => '+ -14', ``` are also possible now. For numbers -= can be built, too: ``` attr => '- -14', ``` Arrays can also be marked to merge with '+' or reduce by '-' as the first item of the array: ``` attr => [ '+', item1, item2, ... ] ``` Result: attr += [ item1, item2, ... ] ``` attr => [ '-', item1, item2, ... ] ``` Result: attr -= [ item1, item2, ... ] That all works for attributes and custom attributes! Finally dictionaries can be merged when a key '+' is set: ``` attr => { '+' => true, 'key1' => 'val1', } ``` Result: ``` attr += { "key1" = "val1" } ``` If 'attr' is a custom attribute this just works since level 3 of the dictionary: ``` vars => { 'level1' => { 'level2' => { 'level3' => { '+' => true, ... }, }, }, }, ``` Parsed to: ``` vars.level1["level2"] += level3 ``` Now it's also possible to add multiple custom attributes: ``` vars => [ { 'a' => '1', 'b' => '2', }, 'config', { 'c' => { 'd' => { '+' => true, 'e' => '5', }, }, }, ], ``` And you'll get: ``` vars.a = "1" vars.b = "2" vars += config vars.c["d"] += { "e" = "5" } ``` Note: Using an Array always means merge '+=' all items to vars. ##### What isn't supported? It's not currently possible to use dictionaries in a string WITH nested array or hash, like ``` attr1 => 'hash1 + { item1 => value1, item2 => [ value1, value2 ], ... ]' attr2 => 'hash2 + { item1 => value1, item2 => { ... },... }' ``` ## Reference See [REFERENCE.md](https://github.com/Icinga/puppet-icinga2/blob/master/REFERENCE.md) ## Release Notes When releasing new versions we refer to [SemVer 1.0.0] for version numbers. All steps required when creating a new release are described in [RELEASE.md](https://github.com/Icinga/puppet-icinga2/blob/master/RELEASE.md) See also [CHANGELOG.md](https://github.com/Icinga/puppet-icinga2/blob/master/CHANGELOG.md) [distributed monitoring]: http://docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/distributed-monitoring [puppetlabs/stdlib]: https://github.com/puppetlabs/puppetlabs-stdlib [puppetlabs/concat]: https://github.com/puppetlabs/puppetlabs-concat [puppetlabs/apt]: https://github.com/puppetlabs/puppetlabs-apt [puppetlabs/chocolatey]: https://github.com/puppetlabs/puppetlabs-chocolatey [puppet/zypprepo]: https://forge.puppet.com/puppet/zypprepo [puppetlabs/mysql]: https://github.com/puppetlabs/puppetlabs-mysql [puppetlabs/puppetlabs-postgresql]: https://github.com/puppetlabs/puppetlabs-postgresql [puppet-icinga2]: https://github.com/icinga/puppet-icinga2 [packages.icinga.com]: https://packages.icinga.com [Chocolatey]: https://chocolatey.org [SemVer 1.0.0]: http://semver.org/spec/v1.0.0.html [CONTRIBUTING.md]: CONTRIBUTING.md [TESTING.md]: TESTING.md [RELEASE.md]: RELEASE.md [CHANGELOG.md]: CHANGELOG.md [AUTHORS]: AUTHORS diff --git a/examples/init_api.pp b/examples/init_api.pp index ad66265..1ff420a 100644 --- a/examples/init_api.pp +++ b/examples/init_api.pp @@ -1,37 +1,37 @@ class { 'icinga2': } class { '::icinga2::feature::api': pki => none, } include ::icinga2::pki::ca #::icinga2::object::apiuser { 'director': # ensure => present, # password => 'Eih5Weefoo2oa8sh', # permissions => [ "*" ], # target => '/etc/icinga2/conf.d/api-users.conf', #} ::icinga2::object::apiuser { 'icingaweb2': ensure => present, - password => '12e2ef553068b519', + password => Sensitive('read(write'), permissions => [ 'status/query', 'actions/*', 'objects/modify/*', 'objects/query/*' ], target => '/etc/icinga2/conf.d/api-users.conf', } ::icinga2::object::apiuser { 'read': ensure => present, password => 'rea)d', permissions => [ { permission => 'objects/query/Host', filter => '{{ regex("^Linux", host.vars.os) }}' }, { permission => 'objects/query/Service', filter => '{{ regex("^Linux", host.vars.os) }}' }, ], target => '/etc/icinga2/conf.d/api-users.conf', } diff --git a/examples/init_elasticsearch.pp b/examples/init_elasticsearch.pp new file mode 100644 index 0000000..92fe176 --- /dev/null +++ b/examples/init_elasticsearch.pp @@ -0,0 +1,5 @@ +include icinga2 + +class { '::icinga2::feature::elasticsearch': + password => Sensitive('super(secret'), +} diff --git a/examples/init_icingadb.pp b/examples/init_icingadb.pp index 9a52a0e..55df234 100644 --- a/examples/init_icingadb.pp +++ b/examples/init_icingadb.pp @@ -1,31 +1,8 @@ -case $::facts['os']['name'] { - 'redhat', 'centos': { - if Integer($::facts['os']['release']['major']) < 8 { - $epel = true - $backports = false - } else { - $epel = false - $backports = false - } - } # RedHat - 'debian', 'ubuntu': { - if $::facts['os']['distro']['codename'] in [ 'stretch', 'trusty' ] { - $epel = false - $backports = true - } - } # Debian - default: { - fail('Your operating system is not supported.') - } +class { '::icinga2': + manage_repos => true, } -class { '::icinga::repos': - manage_release => false, - manage_testing => true, - manage_epel => $epel, - configure_backports => $backports, +class { '::icinga2::feature::icingadb': + password => Sensitive('super(secret'), + #password => 'super(secret', } - -include ::icinga2 -include ::icinga2::feature::icingadb - diff --git a/examples/init_idomysql.pp b/examples/init_idomysql.pp index 00eb654..f9eaea3 100644 --- a/examples/init_idomysql.pp +++ b/examples/init_idomysql.pp @@ -1,24 +1,28 @@ +$password = Sensitive('super(secret') + include ::mysql::server mysql::db { 'icinga2': user => 'icinga2', - password => 'supersecret', + password => $password, host => 'localhost', grant => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE VIEW', 'CREATE', 'INDEX', 'EXECUTE', 'ALTER'], } class { '::icinga2': manage_repos => true, } +notice($password) + class{ '::icinga2::feature::idomysql': user => 'icinga2', - password => 'supersecret', + password => $password, database => 'icinga2', import_schema => true, require => Mysql::Db['icinga2'], cleanup => { hostchecks_age => '3m', servicechecks_age => '36h', }, } diff --git a/examples/init_idopgsql.pp b/examples/init_idopgsql.pp index 975f21c..ef7452b 100644 --- a/examples/init_idopgsql.pp +++ b/examples/init_idopgsql.pp @@ -1,18 +1,22 @@ +$password = Sensitive('super(secret') + include ::postgresql::server postgresql::server::db { 'icinga2': user => 'icinga2', - password => postgresql_password('icinga2', 'supersecret'), + password => postgresql::postgresql_password('icinga2', $password.unwrap), } class{ 'icinga2': manage_repos => true, } +notice($password) + class{ 'icinga2::feature::idopgsql': user => 'icinga2', - password => 'supersecret', + password => $password, database => 'icinga2', import_schema => true, require => Postgresql::Server::Db['icinga2'], } diff --git a/examples/init_influxdb.pp b/examples/init_influxdb.pp index 7ee37f6..67db40e 100644 --- a/examples/init_influxdb.pp +++ b/examples/init_influxdb.pp @@ -1,5 +1,11 @@ class { 'icinga2': manage_repos => true, } -include icinga2::feature::influxdb +class { '::icinga2::feature::influxdb': + password => Sensitive('super(secret'), + basic_auth => { + username => 'icinga2', + password => Sensitive('super(secret'), + }, +} diff --git a/examples/init_influxdb2.pp b/examples/init_influxdb2.pp index 93c415a..60a1cd2 100644 --- a/examples/init_influxdb2.pp +++ b/examples/init_influxdb2.pp @@ -1,10 +1,11 @@ class { 'icinga2': manage_repos => true, } class{ 'icinga2::feature::influxdb2': ensure => present, organization => 'ICINGA', bucket => 'icinga2', - auth_token => 'supersecret', +# auth_token => 'super(secret', + auth_token => Sensitive('super(secret'), } diff --git a/examples/init_master.pp b/examples/init_server.pp similarity index 59% rename from examples/init_master.pp rename to examples/init_server.pp index d670948..ccd5a40 100644 --- a/examples/init_master.pp +++ b/examples/init_server.pp @@ -1,20 +1,20 @@ class { '::icinga2': manage_repos => true, constants => { - 'NodeName' => 'master.localdomain', - 'ZoneName' => 'master', - 'TicketSalt' => '5a3d695b8aef8f18452fc494593056a4', + 'NodeName' => 'server.localdomain', + 'ZoneName' => 'main', + 'TicketSalt' => Sensitive('5a3d695b8aef8f18452fc494593056a4'), } } class { '::icinga2::feature::api': pki => 'none', zones => { - 'master' => { + 'main' => { 'endpoints' => [ 'NodeName' ], }, } } class { '::icinga2::pki::ca': } diff --git a/examples/init_slave.pp b/examples/init_worker.pp similarity index 54% rename from examples/init_slave.pp rename to examples/init_worker.pp index 9ef39f0..9805a5a 100644 --- a/examples/init_slave.pp +++ b/examples/init_worker.pp @@ -1,32 +1,33 @@ -$master_cert = 'master.localdomain' -$master_ip = '192.168.5.16' +$server_cert = 'server.localdomain' +$server_ip = '192.168.5.23' class { '::icinga2': manage_repos => true, constants => { - 'NodeName' => 'slave.localdomain', + 'NodeName' => 'worker.localdomain', }, } class { '::icinga2::feature::api': pki => 'icinga2', - ca_host => $master_ip, + ca_host => $server_ip, +# ticket_salt => Sensitive('5a3d695b8aef8f18452fc494593056a4'), ticket_salt => '5a3d695b8aef8f18452fc494593056a4', accept_config => true, accept_commands => true, endpoints => { 'NodeName' => {}, - "${master_cert}" => { - 'host' => $master_ip, + "${server_cert}" => { + 'host' => $server_ip, } }, zones => { 'ZoneName' => { 'endpoints' => [ 'NodeName' ], - 'parent' => 'master', + 'parent' => 'main', }, - 'master' => { - 'endpoints' => [ $master_cert ], + 'main' => { + 'endpoints' => [ $server_cert ], }, } } diff --git a/examples/init_slave_validate.pp b/examples/init_worker_validate.pp similarity index 64% rename from examples/init_slave_validate.pp rename to examples/init_worker_validate.pp index 9cc54bd..2ff6844 100644 --- a/examples/init_slave_validate.pp +++ b/examples/init_worker_validate.pp @@ -1,36 +1,36 @@ -$master_cert = 'master.localdomain' -$master_ip = '192.168.5.12' +$server_cert = 'server.localdomain' +$server_ip = '192.168.5.23' -# get it on CA host 'openssl x509 -noout -fingerprint -sha256 -inform pem -in /var/lib/icinga2/certs/master.localdomain.crt' +# get it on CA host 'openssl x509 -noout -fingerprint -sha256 -inform pem -in /var/lib/icinga2/certs/server.localdomain.crt' $fingerprint = 'D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:50:68:01:D8:98:82:1B:14:8A:6A:89:4B:7A:40:32:99:3D:96:72:72' class { '::icinga2': manage_repos => true, constants => { - 'NodeName' => 'slave.localdomain', + 'NodeName' => 'worker.localdomain', }, } class { '::icinga2::feature::api': pki => 'icinga2', - ca_host => $master_ip, + ca_host => $server_ip, ticket_salt => '5a3d695b8aef8f18452fc494593056a4', accept_config => true, accept_commands => true, endpoints => { 'NodeName' => {}, - "${master_cert}" => { - 'host' => $master_ip, + "${server_cert}" => { + 'host' => $server_ip, } }, zones => { 'ZoneName' => { 'endpoints' => [ 'NodeName' ], - 'parent' => 'master', + 'parent' => 'main', }, - 'master' => { - 'endpoints' => [ $master_cert ], + 'main' => { + 'endpoints' => [ $server_cert ], }, }, fingerprint => $fingerprint, } diff --git a/lib/puppet/functions/icinga2/icinga2_ticket_id.rb b/lib/puppet/functions/icinga2/icinga2_ticket_id.rb index ef2674b..6bd8dba 100644 --- a/lib/puppet/functions/icinga2/icinga2_ticket_id.rb +++ b/lib/puppet/functions/icinga2/icinga2_ticket_id.rb @@ -1,47 +1,35 @@ -# This is an autogenerated function, ported from the original legacy version. -# It /should work/ as is, but will not have all the benefits of the modern -# function API. You should see the function docs to learn how to add function -# signatures for type safety and to document this function using puppet-strings. -# -# https://puppet.com/docs/puppet/latest/custom_functions_ruby.html -# -# ---- original file header ---- -require File.join(File.dirname(__FILE__), '../../..', 'puppet_x/icinga2/pbkdf2.rb') +# frozen_string_literal: true -# ---- original file header ---- -# +require File.join(File.dirname(__FILE__), '../../..', 'puppet_x/icinga2/pbkdf2.rb') # @summary # Summarise what the function does here # Puppet::Functions.create_function(:'icinga2::icinga2_ticket_id') do - # @param args - # The original array of arguments. Port this to individually managed params - # to get the full benefit of the modern function API. + # @param cn + # The common name of the Icinga host certificate. + # + # @param salt + # The ticket salt of the Icinga CA. # - # @return [Data type] - # Describe what the function returns here + # @return [String] + # Calculated ticket to receive a certificate. # - dispatch :default_impl do - # Call the method named 'default_impl' when this is matched - # Port this to match individual params for better type safety - repeated_param 'Any', :args + dispatch :ticket do + required_param 'String', :cn + required_param 'Variant[String, Sensitive[String]]', :salt + return_type 'String' end - def default_impl(*args) - raise Puppet::ParseError, 'Must provide exactly two arguments to icinga2_ticket_id' if args.length != 2 - - if !args[0] || args[0] == '' - raise Puppet::ParseError, 'first argument (cn) can not be empty for icinga2_ticket_id' - end - if !args[1] || args[1] == '' - raise Puppet::ParseError, 'second argument (salt) can not be empty for icinga2_ticket_id' + def ticket(cn, salt) + if salt.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + salt = salt.unwrap end PBKDF2.new( - password: args[0], - salt: args[1], + password: cn, + salt: salt, iterations: 50_000, hash_function: OpenSSL::Digest.new('sha1'), ).hex_string end end diff --git a/lib/puppet_x/icinga2/utils.rb b/lib/puppet_x/icinga2/utils.rb index 47075d5..f92b46d 100644 --- a/lib/puppet_x/icinga2/utils.rb +++ b/lib/puppet_x/icinga2/utils.rb @@ -1,365 +1,377 @@ # == Helper function attributes # # Returns formatted attributes for objects as strings. # # === Common Explanation: # # To generate a valid Icinga 2 configuration all object attributes are parsed. This # simple parsing algorithm takes a decision for each attribute, whether part of the # string is to be quoted or not, and how an array or dictionary is to be formatted. # # Parsing of a single attribute can be disabled by tagging it with -: at the front # of the string. # # attr => '-:"unparsed string with quotes"' # # An array, a hash or a string can be assigned to an object attribute. True and false # are also valid values. # # Hashes and arrays are created recursively, and all parts – such as single items of an array, # keys and its values – are parsed separately as strings. # # Strings are parsed in chunks, by splitting the original string into separate substrings # at specific keywords (operators) such as +, -, in, &&, ||, etc. # # NOTICE: This splitting only works for keywords that are surrounded by whitespace, e.g.: # # attr => 'string1 + string2 - string3' # # The algorithm will loop over the parameter and start by splitting it into 'string1' and 'string2 - string3'. # 'string1' will be passed to the sub function 'value_types' and then the algorithm will continue parsing # the rest of the string ('string2 - string3'), splitting it, passing it to value_types, etc. # # Brackets are parsed for expressions: # # attr => '3 * (value1 - value2) / 2' # # The parser also detects function calls and will parse all parameters separately. # # attr => 'function(param1, param2, ...)' # # True and false can be used as either booleans or strings. # # attrs => true or attr => 'true' # # In Icinga you can write your own lambda functions with {{ ... }}. For puppet use: # # attrs => '{{ ... }}' # # The parser analyzes which parts of the string have to be quoted and which do not. # # As a general rule, all fragments are quoted except for the following: # # - boolean: true, false # - numbers: 3 or 2.5 # - time intervals: 3m or 2.5h (s = seconds, m = minutes, h = hours, d = days) # - functions: {{ ... }} or function () {} # - all constants, which are declared in the constants parameter in main class icinga2: # NodeName # - names of attributes that belong to the same type of object: # e.g. name and check_command for a host object # - all attributes or variables (custom attributes) from the host, service or user contexts: # host.name, service.check_command, user.groups, ... # # Assignment with += and -=: # # Now it's possible to build an Icinga DSL code snippet like # # vars += config # # simply use a string with the prefix '+ ', e.g. # # vars => '+ config', # # The blank between + and the proper string 'config' is imported for the parser because numbers # # attr => '+ -14', # # are also possible now. For numbers -= can be built, too: # # attr => '- -14', # # Arrays can also be marked to merge with '+' or reduce by '-' as the first item of the array: # # attr => [ '+', item1, item2, ... ] # # Result: attr += [ item1, item2, ... ] # # attr => [ '-' item1, item2, ... ] # # Result: attr -= [ item1, item2, ... ] # # That all works for attributes and custom attributes! # # Finally dictionaries can be merged when a key '+' is set: # # attr => { # '+' => true, # 'key1' => 'val1', # } # # Result: # # attr += { # "key1" = "val1" # } # # If 'attr' is a custom attribute this just works since level 3 of the dictionary: # # vars => { # 'level1' => { # 'level2' => { # 'level3' => { # '+' => true, # ... # }, # }, # }, # }, # # Parsed to: # # vars.level1["level2"] += level3 # # Now it's also possible to add multiple custom attributes: # # vars => [ # { # 'a' => '1', # 'b' => '2', # }, # 'config', # { # 'c' => { # 'd' => { # '+' => true, # 'e' => '5', # }, # }, # }, # ], # # And you'll get: # # vars.a = "1" # vars.b = "2" # vars += config # vars.c["d"] += { # "e" = "5" # } # # Note: Using an Array always means merge '+=' all items to vars. # # === What isn't supported? # # It's not currently possible to use dictionaries in a string WITH nested array or hash, like # # attr1 => 'hash1 + { item1 => value1, item2 => [ value1, value2 ], ... ]' # attr2 => 'hash2 + { item1 => value1, item2 => { ... },... }' # # require 'puppet' module Puppet module Icinga2 module Utils def self.attributes(attrs, globals, consts, indent=2) def self.value_types(value) if value =~ /^-?\d+\.?\d*[dhms]?$/ || value =~ /^(true|false|null)$/ || value =~ /^!?(host|service|user)\./ || value =~ /^\{{2}.*\}{2}$/ result = value else if $constants.index { |x| if $hash_attrs.include?(x) then value =~ /^!?(#{x})(\..+$|$)/ else value =~ /^!?#{x}$/ end } result = value else result = value.dump end end return result end def self.attribute_types(attr) if attr =~ /^[a-zA-Z0-9_]+$/ result = attr else result = "\"#{attr}\"" end return result end def self.parse(row) result = '' # parser is disabled if row =~ /^-:(.*)$/m return $1 end if row =~ /^\{{2}(.+)\}{2}$/m # scan function result += "{{%s}}" % [ $1 ] elsif row =~ /^(.+)\s([\+-]|\*|\/|==|!=|&&|\|{2}|in)\s\{{2}(.+)\}{2}$/m # scan expression + function (function should contain expressions, but we donno parse it) result += "%s %s {{%s}}" % [ parse($1), $2, $3 ] elsif row =~ /^(.+)\s([\+-]|\*|\/|==|!=|&&|\|{2}|in)\s(.+)$/ # scan expression result += "%s %s %s" % [ parse($1), $2, parse($3) ] else if row =~ /^(.+)\((.*)$/ result += "%s(%s" % [ $1, $2.split(',').map {|x| parse(x.lstrip)}.join(', ') ] elsif row =~ /^(.*)\)(.+)?$/ # closing bracket ) with optional access of an attribute e.g. '.arguments' result += "%s)%s" % [ $1.split(',').map {|x| parse(x.lstrip)}.join(', '), $2 ] elsif row =~ /^\((.*)$/ result += "(%s" % [ parse($1) ] elsif row =~ /^\s*\[\s*(.*)\s*\]\s?(.+)?$/ # parse array result += "[ %s]" % [ process_array($1.split(',')) ] result += " %s" % [ parse($2) ] if $2 elsif row =~ /^\s*\{\s*(.*)\s*\}\s?(.+)?$/ # parse hash result += "{\n%s}" % [ process_hash(Hash[$1.gsub(/\s*=>\s*|\s*,\s*/, ',').split(',').each_slice(2).to_a]) ] result += " %s" % [ parse($2) ] if $2 else result += value_types(row.to_s.strip) end end return result.gsub(/" in "/, ' in ') end def self.process_array(items, indent=2) result = '' items.each do |value| + if value.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + value = value.unwrap + value = '-:"' + value + '"' if value.is_a?(String) + end if value.is_a?(Hash) result += "\n%s{\n%s%s}, " % [ ' ' * indent, process_hash(value, indent + 2), ' ' * indent ] elsif value.is_a?(Array) result += "[ %s], " % [ process_array(value, indent+2) ] else result += "%s, " % [ parse(value) ] if value end end return result end def self.process_hash(attrs, indent=2, level=3, prefix=' '*indent) result = '' attrs.each do |attr, value| + if value.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + value = value.unwrap + value = '-:"' + value + '"' if value.is_a?(String) + end if value.is_a?(Hash) op = '+' if value.delete('+') if value.empty? result += case level when 1 then "%s%s #{op}= {}\n" % [ prefix, attribute_types(attr) ] when 2 then "%s[\"%s\"] #{op}= {}\n" % [ prefix, attr ] else "%s%s #{op}= {}\n" % [ prefix, attribute_types(attr) ] end else result += case level when 1 then process_hash(value, indent, 2, "%s%s" % [ prefix, attr ]) when 2 then "%s[\"%s\"] #{op}= {\n%s%s}\n" % [ prefix, attr, process_hash(value, indent), ' ' * (indent-2) ] else "%s%s #{op}= {\n%s%s}\n" % [ prefix, attribute_types(attr), process_hash(value, indent+2), ' ' * indent ] end end elsif value.is_a?(Array) op = value.delete_at(0) if value[0] == '+' or value[0] == '-' result += case level when 2 then "%s[\"%s\"] #{op}= [ %s]\n" % [ prefix, attribute_types(attr), process_array(value) ] else "%s%s #{op}= [ %s]\n" % [ prefix, attribute_types(attr), process_array(value) ] end else # String: attr = '+ value' -> attr += 'value' if value =~ /^([\+,-])\s+/ operator = "#{$1}=" value = value.sub(/^[\+,-]\s+/, '') else operator = '=' end if level > 1 if level == 3 result += "%s%s #{operator} %s\n" % [ prefix, attribute_types(attr), parse(value) ] if value != :nil else result += "%s[\"%s\"] #{operator} %s\n" % [ prefix, attr, parse(value) ] if value != :nil end else result += "%s%s #{operator} %s\n" % [ prefix, attr, parse(value) ] if value != :nil end end end return result end # globals (params.pp) and all keys of attrs hash itselfs must not quoted $constants = globals.concat(consts.keys) << "name" # select all attributes and constants if there value is a hash $hash_attrs = attrs.merge(consts).select { |x,y| y.is_a?(Hash) }.keys # initialize returned configuration config = '' attrs.each do |attr, value| + if value.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + value = value.unwrap + value = '-:"' + value + '"' if value.is_a?(String) + end if attr =~ /^(assign|ignore) where$/ value.each do |x| config += "%s%s %s\n" % [ ' ' * indent, attr, parse(x) ] if x end elsif attr == 'vars' if value.is_a?(Hash) # delete pair of key '+' because a merge at this point is not allowed value.delete('+') config += process_hash(value, indent+2, 1, "%s%s." % [ ' ' * indent, attr]) elsif value.is_a?(Array) value.each do |item| if item.is_a?(String) config += "%s%s += %s\n" % [ ' ' * indent, attr, item.sub(/^[\+,-]\s+/, '') ] else item.delete('+') if item.empty? config += "%s%s += {}\n" % [ ' ' * indent, attr] else config += process_hash(item, indent+2, 1, "%s%s." % [ ' ' * indent, attr]) end end end else op = '+' if value =~ /^\+\s+/ config += "%s%s #{op}= %s\n" % [ ' ' * indent, attr, parse(value.sub(/^\+\s+/, '')) ] end else if value.is_a?(Hash) op = '+' if value.delete('+') unless value.empty? config += "%s%s #{op}= {\n%s%s}\n" % [ ' ' * indent, attr, process_hash(value, indent+2), ' ' * indent ] else config += "%s%s #{op}= {}\n" % [ ' ' * indent, attr ] end elsif value.is_a?(Array) op = value.delete_at(0) if value[0] == '+' or value[0] == '-' config += "%s%s #{op}= [ %s]\n" % [ ' ' * indent, attr, process_array(value) ] else # String: attr = '+config' -> attr += config if value =~ /^([\+,-])\s+/ config += "%s%s #{$1}= %s\n" % [ ' ' * indent, attr, parse(value.sub(/^[\+,-]\s+/, '')) ] else config += "%s%s = %s\n" % [ ' ' * indent, attr, parse(value) ] end end end end return config end end end end diff --git a/manifests/feature/api.pp b/manifests/feature/api.pp index ed98506..d4af1d6 100644 --- a/manifests/feature/api.pp +++ b/manifests/feature/api.pp @@ -1,383 +1,391 @@ # @summary # Configures the Icinga 2 feature api. # # @example Use the puppet certificates and key copy these files to the cert directory named to 'hostname.key', 'hostname.crt' and 'ca.crt' if the contant NodeName is set to 'hostname'. # include ::icinga2::feature::api # # @example To use your own certificates and key as file resources if the contant NodeName is set to fqdn (default) do: # class { 'icinga2::feature::api': # pki => 'none', # } # # File { # owner => 'icinga', # group => 'icinga', # } # # file { "/var/lib/icinga2/certs/${::fqdn}.key": # ensure => file, # tag => 'icinga2::config::file', # source => "puppet:///modules/profiles/private_keys/${::fqdn}.key", # } # ... # # @example If you like to manage the certificates and the key as strings in base64 encoded format: # class { 'icinga2::feature::api': # pki => 'none', # ssl_cacert => '-----BEGIN CERTIFICATE----- ...', # ssl_key => '-----BEGIN RSA PRIVATE KEY----- ...', # ssl_cert => '-----BEGIN CERTIFICATE----- ...', # } # # @example Fine tune TLS settings # class { '::icinga2::feature::api': # ssl_protocolmin => 'TLSv1.2', # ssl_cipher_list => 'HIGH:MEDIUM:!aNULL:!MD5:!RC4', # } # # @example Transfer a CA certificate and key from an existing CA by using the file resource: # include ::icinga2 # # file { '/var/lib/icinga2/ca/ca.crt': # source => '...', # tag => 'icinga2::config::file', # } # # file { '/var/lib/icinga2/ca/ca.key': # source => '...', # tag => 'icinga2::config::file', # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature api, absent disabled it. # # @param [Enum['ca', 'icinga2', 'none', 'puppet']] pki # Provides multiple sources for the certificate, key and ca. # - puppet: Copies the key, cert and CAcert from the Puppet ssl directory to the cert directory # /var/lib/icinga2/certs on Linux and C:/ProgramData/icinga2/var/lib/icinga2/certs on Windows. # Please note that Puppet 7 uses an intermediate CA by default and Icinga cannot handle # its CA certificate, see [Icinga Issue](https://github.com/Icinga/icinga2/pull/8859). # - icinga2: Uses the icinga2 CLI to generate a Certificate Request and Key to obtain a signed # Certificate from 'ca_host' using the icinga2 ticket mechanism. # In case the 'ticket_salt' has been configured the ticket_id will be generated # by the module in a custom function that imitates the icinga ticket generation. # The 'ticket_id' parameter can be used to directly set an ticket_id. # - none: Does nothing and you either have to manage the files yourself as file resources # or use the ssl_key, ssl_cert, ssl_cacert parameters. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in cert directory. This parameter # requires pki to be set to 'none'. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded string to store in cert directory This parameter # requires pki to be set to 'none'. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded string to store in cert directory. This parameter # requires pki to be set to 'none'. # # @param [Optional[Stdlib::Absolutepath]] ssl_crl # Optional location of the certificate revocation list. # # @param [Optional[Boolean]] accept_config # Accept zone configuration. # # @param [Optional[Boolean]] accept_commands # Accept remote commands. # # @param [Optional[Integer[0]]] max_anonymous_clients # Limit the number of anonymous client connections (not configured endpoints and signing requests). # # @param [Optional[Stdlib::Host]] ca_host # This host will be connected to request the certificate. Set this if you use the icinga2 pki. # # @param [Stdlib::Port::Unprivileged] ca_port # Port of the 'ca_host'. # # @param [Optional[Icinga2::Fingerprint]] fingerprint # Fingerprint of the CA host certificate for validation. Requires pki is set to `icinga2`. # You can get the fingerprint via 'openssl x509 -noout -fingerprint -sha256 -inform pem -in [certificate-file.crt]' # on your CA host. (Icinga2 versions before 2.12.0 require '-sha1' as digest algorithm.) # -# @param [String] ticket_salt +# @param Variant[[String, Sensitive[String]] ticket_salt # Salt to use for ticket generation. The salt is stored to api.conf if none or ca is chosen for pki. # Defaults to constant TicketSalt. Keep in mind this parameter is parsed so please use only alpha numric # characters as salt or a constant. # -# @param [Optional[String]] ticket_id +# @param [Optional[Variant[String, Sensitive[String]]]] ticket_id # If a ticket_id is given it will be used instead of generating an ticket_id. # The ticket_id will be used only when requesting a certificate from the ca_host # in case the pki is set to 'icinga2'. # # @param [Hash[String, Hash]] endpoints # Hash to configure endpoint objects. `NodeName` is a icnga2 constant. # # @param [Hash[String, Hash]] zones # Hash to configure zone objects. `ZoneName` and `NodeName` are icinga2 constants. # # @param [Optional[Enum['TLSv1', 'TLSv1.1', 'TLSv1.2']]] ssl_protocolmin # Minimal TLS version to require. # # @param [Optional[Icinga2::Interval]] ssl_handshake_timeout # TLS Handshake timeout. # # @param [Optional[Icinga2::Interval]] connect_timeout # Timeout for establishing new connections. # # @param [Optional[String]] ssl_cipher_list # List of allowed TLS ciphers, to finetune encryption. # # @param [Optional[Stdlib::Host]] bind_host # The IP address the api listener will be bound to. # # @param [Optional[Stdlib::Port::Unprivileged]] bind_port # The port the api listener will be bound to. # # @param [Optional[Array[String]]] access_control_allow_origin # Specifies an array of origin URLs that may access the API. # # @param [Optional[Boolean]] access_control_allow_credentials # Indicates whether or not the actual request can be made using credentials. # # @param [Optional[String]] access_control_allow_headers # Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. # # @param [Optional[Array[Enum['GET', 'POST', 'PUT', 'DELETE']]]] access_control_allow_methods # Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. # # @param [Optional[String]] environment # Used as suffix in TLS SNI extension name; default from constant ApiEnvironment, which is empty. # class icinga2::feature::api( Enum['absent', 'present'] $ensure = present, Enum['ca', 'icinga2', 'none', 'puppet'] $pki = 'icinga2', Optional[Stdlib::Absolutepath] $ssl_crl = undef, Optional[Boolean] $accept_config = undef, Optional[Boolean] $accept_commands = undef, Optional[Integer[0]] $max_anonymous_clients = undef, Optional[Stdlib::Host] $ca_host = undef, Stdlib::Port::Unprivileged $ca_port = 5665, - String $ticket_salt = 'TicketSalt', - Optional[String] $ticket_id = undef, + Variant[String, Sensitive[String]] $ticket_salt = 'TicketSalt', + Optional[Variant[String, Sensitive[String]]] $ticket_id = undef, Hash[String, Hash] $endpoints = { 'NodeName' => {} }, Hash[String, Hash] $zones = { 'ZoneName' => { endpoints => [ 'NodeName' ] } }, Optional[Stdlib::Base64] $ssl_key = undef, Optional[Stdlib::Base64] $ssl_cert = undef, Optional[Stdlib::Base64] $ssl_cacert = undef, Optional[Enum['TLSv1', 'TLSv1.1', 'TLSv1.2']] $ssl_protocolmin = undef, Optional[Icinga2::Interval] $ssl_handshake_timeout = undef, Optional[Icinga2::Interval] $connect_timeout = undef, Optional[String] $ssl_cipher_list = undef, Optional[Stdlib::Host] $bind_host = undef, Optional[Stdlib::Port::Unprivileged] $bind_port = undef, Optional[Array[Enum['GET', 'POST', 'PUT', 'DELETE']]] $access_control_allow_methods = undef, Optional[Array[String]] $access_control_allow_origin = undef, Optional[Boolean] $access_control_allow_credentials = undef, Optional[String] $access_control_allow_headers = undef, Optional[Icinga2::Fingerprint] $fingerprint = undef, Optional[String] $environment = undef, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } # cert directory must exists and icinga binary is required for icinga2 pki require ::icinga2::install $icinga2_bin = $::icinga2::globals::icinga2_bin $conf_dir = $::icinga2::globals::conf_dir $cert_dir = $::icinga2::globals::cert_dir $ca_dir = $::icinga2::globals::ca_dir $user = $::icinga2::globals::user $group = $::icinga2::globals::group $node_name = $::icinga2::_constants['NodeName'] $_ssl_key_mode = $::facts['os']['family'] ? { 'windows' => undef, default => '0600', } $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } File { owner => $user, group => $group, } # Set defaults for certificate stuff $_ssl_key_path = "${cert_dir}/${node_name}.key" $_ssl_cert_path = "${cert_dir}/${node_name}.crt" $_ssl_csr_path = "${cert_dir}/${node_name}.csr" $_ssl_cacert_path = "${cert_dir}/ca.crt" # handle the certificate's stuff case $pki { 'puppet': { $_ticket_salt = undef file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, source => $::facts['icinga2_puppet_hostprivkey'], tag => 'icinga2::config::file', show_diff => false, backup => false, } file { $_ssl_cert_path: ensure => file, source => $::facts['icinga2_puppet_hostcert'], tag => 'icinga2::config::file', } file { $_ssl_cacert_path: ensure => file, source => $::facts['icinga2_puppet_localcacert'], tag => 'icinga2::config::file', } } # puppet 'none': { # non means you manage the CA on your own and so # the salt has to be stored in api.conf - $_ticket_salt = $ticket_salt + $_ticket_salt = if $ticket_salt =~ Sensitive { + $ticket_salt + } else { + Sensitive($ticket_salt) + } if $ssl_key { $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $_ssl_key, tag => 'icinga2::config::file', show_diff => false, backup => false, } } if $ssl_cert { $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $_ssl_cert, tag => 'icinga2::config::file', } } if $ssl_cacert { $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $_ssl_cacert, tag => 'icinga2::config::file', } } } # none # icinga2 as default default: { $_ticket_salt = undef $trusted_cert = "${cert_dir}/trusted-cert.crt" $cmd_pki_get_cert = "\"${icinga2_bin}\" pki save-cert --host ${ca_host} --port ${ca_port} --key ${_ssl_key_path} --cert ${_ssl_cert_path} --trustedcert ${trusted_cert}" if($ticket_id) { - $_ticket = "--ticket ${ticket_id}" + $_ticket = if $ticket_id =~ Sensitive { + "--ticket ${ticket_id.unwrap}" + } else { + "--ticket ${ticket_id}" + } } elsif($ticket_salt != 'TicketSalt') { - $_ticket = "--ticket ${icinga2_ticket_id($node_name, $ticket_salt)}" + $_ticket = "--ticket ${icinga2::icinga2_ticket_id($node_name, $ticket_salt)}" } else { $_ticket = '' } if $fingerprint { $_fingerprint = upcase(regsubst($fingerprint, ':', ' ', 'G')) if $::facts['os']['family'] != 'Windows' { $_cmd_pki_get_cert = "${cmd_pki_get_cert} |grep '${_fingerprint}\s*$'" } else { $_cmd_pki_get_cert = "cmd.exe /c \"${cmd_pki_get_cert} |findstr /R /C:\"${_fingerprint}\"\"" } } else { $_cmd_pki_get_cert = $cmd_pki_get_cert } $_env = $::facts['kernel'] ? { 'windows' => undef, default => ["ICINGA2_USER=${user}", "ICINGA2_GROUP=${group}"], } Exec { environment => $_env, notify => Class['::icinga2::service'], } exec { 'icinga2 pki create key': command => "\"${icinga2_bin}\" pki new-cert --cn ${node_name} --key ${_ssl_key_path} --cert ${_ssl_cert_path}", creates => $_ssl_key_path, } -> exec { 'icinga2 pki get trusted-cert': path => $::facts['path'], command => $_cmd_pki_get_cert, creates => $trusted_cert, } -> exec { 'icinga2 pki request': command => "\"${icinga2_bin}\" pki request --host ${ca_host} --port ${ca_port} --ca ${_ssl_cacert_path} --key ${_ssl_key_path} --cert ${_ssl_cert_path} --trustedcert ${trusted_cert} ${_ticket}", # lint:ignore:140chars creates => $_ssl_cacert_path, } } # icinga2 } # case pki # compose attributes $attrs = { crl_path => $ssl_crl, accept_commands => $accept_commands, accept_config => $accept_config, max_anonymous_clients => $max_anonymous_clients, ticket_salt => $_ticket_salt, tls_protocolmin => $ssl_protocolmin, tls_handshake_timeout => $ssl_handshake_timeout, connect_timeout => $connect_timeout, cipher_list => $ssl_cipher_list, bind_host => $bind_host, bind_port => $bind_port, access_control_allow_origin => $access_control_allow_origin, access_control_allow_credentials => $access_control_allow_credentials, access_control_allow_headers => $access_control_allow_headers, access_control_allow_methods => $access_control_allow_methods, environment => $environment, } # create endpoints and zones create_resources('icinga2::object::endpoint', $endpoints) create_resources('icinga2::object::zone', $zones) # create object icinga2::object { 'icinga2::object::ApiListener::api': object_name => 'api', object_type => 'ApiListener', attrs => delete_undef_values($attrs), attrs_list => keys($attrs), target => "${conf_dir}/features-available/api.conf", order => 10, notify => $_notify, } # manage feature icinga2::feature { 'api': ensure => $ensure, } } diff --git a/manifests/feature/elasticsearch.pp b/manifests/feature/elasticsearch.pp index 774de05..e0dc9f1 100644 --- a/manifests/feature/elasticsearch.pp +++ b/manifests/feature/elasticsearch.pp @@ -1,228 +1,229 @@ # @summary # Configures the Icinga 2 feature elasticsearch. # # @example # class { 'icinga2::feature::elasticsearch': # host => "10.10.0.15", # index => "icinga2" # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature elasticsearch, absent disables it. # # @param [Optional[Stdlib::Host]] host # Elasticsearch host address. # # @param [Optional[Stdlib::Port::Unprivileged]] port # Elasticsearch HTTP port. # # @param [Optional[String]] index # Elasticsearch index name. # # @param [Optional[String]] username # Elasticsearch user name. # -# @param [Optional[String]] password +# @param [Optional[Variant[String, Sensitive[String]]]] password # Elasticsearch user password. The password parameter isn't parsed anymore. # # @param [Optional[Boolean]] enable_ssl # Either enable or disable SSL. Other SSL parameters are only affected if this is set to 'true'. # # @param [Optional[Boolean]] ssl_noverify # Disable TLS peer verification. # # @param [Optional[Stdlib::Absolutepath]] ssl_key_path # Location of the private key. # # @param [Optional[Stdlib::Absolutepath]] ssl_cert_path # Location of the certificate. # # @param [Optional[Stdlib::Absolutepath]] ssl_cacert_path # Location of the CA certificate. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in spicified ssl_key_path file. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded to store in spicified ssl_cert_path file. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded string to store in spicified ssl_cacert_path file. # # @param [Optional[Boolean]] enable_send_perfdata # Whether to send check performance data metrics. # # @param [Optional[Icinga2::Interval]] flush_interval # How long to buffer data points before transferring to Elasticsearch. # # @param [Optional[Integer]] flush_threshold # How many data points to buffer before forcing a transfer to Elasticsearch. # # @param [Optional[Boolean]] enable_ha # Enable the high availability functionality. Only valid in a cluster setup. # class icinga2::feature::elasticsearch( - Enum['absent', 'present'] $ensure = present, - Optional[Stdlib::Host] $host = undef, - Optional[Stdlib::Port::Unprivileged] $port = undef, - Optional[String] $index = undef, - Optional[String] $username = undef, - Optional[String] $password = undef, - Optional[Boolean] $enable_ssl = undef, - Optional[Boolean] $ssl_noverify = undef, - Optional[Stdlib::Absolutepath] $ssl_key_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, - Optional[Stdlib::Base64] $ssl_key = undef, - Optional[Stdlib::Base64] $ssl_cert = undef, - Optional[Stdlib::Base64] $ssl_cacert = undef, - Optional[Boolean] $enable_send_perfdata = undef, - Optional[Icinga2::Interval] $flush_interval = undef, - Optional[Integer] $flush_threshold = undef, - Optional[Boolean] $enable_ha = undef, + Enum['absent', 'present'] $ensure = present, + Optional[Stdlib::Host] $host = undef, + Optional[Stdlib::Port::Unprivileged] $port = undef, + Optional[String] $index = undef, + Optional[String] $username = undef, + Optional[Variant[String, Sensitive[String]]] $password = undef, + Optional[Boolean] $enable_ssl = undef, + Optional[Boolean] $ssl_noverify = undef, + Optional[Stdlib::Absolutepath] $ssl_key_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, + Optional[Stdlib::Base64] $ssl_key = undef, + Optional[Stdlib::Base64] $ssl_cert = undef, + Optional[Stdlib::Base64] $ssl_cacert = undef, + Optional[Boolean] $enable_send_perfdata = undef, + Optional[Icinga2::Interval] $flush_interval = undef, + Optional[Integer] $flush_threshold = undef, + Optional[Boolean] $enable_ha = undef, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $user = $::icinga2::globals::user $group = $::icinga2::globals::group $conf_dir = $::icinga2::globals::conf_dir $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } File { owner => $user, group => $group, } if $enable_ssl { $ssl_dir = $::icinga2::globals::cert_dir $_ssl_key_mode = $::facts['kernel'] ? { 'windows' => undef, default => '0600', } # Set defaults for certificate stuff and/or do validation if $ssl_key { if $ssl_key_path { $_ssl_key_path = $ssl_key_path } else { $_ssl_key_path = "${ssl_dir}/ElasticsearchWriter_elasticsearch.key" } $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $_ssl_key, show_diff => false, tag => 'icinga2::config::file', } } else { $_ssl_key_path = $ssl_key_path } if $ssl_cert { if $ssl_cert_path { $_ssl_cert_path = $ssl_cert_path } else { $_ssl_cert_path = "${ssl_dir}/ElasticsearchWriter_elasticsearch.crt" } $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $_ssl_cert, tag => 'icinga2::config::file', } } else { $_ssl_cert_path = $ssl_cert_path } if $ssl_cacert { if $ssl_cacert_path { $_ssl_cacert_path = $ssl_cacert_path } else { $_ssl_cacert_path = "${ssl_dir}/ElasticsearchWriter_elasticsearch_ca.crt" } $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $_ssl_cacert, tag => 'icinga2::config::file', } } else { $_ssl_cacert_path = $ssl_cacert_path } $attrs_ssl = { enable_tls => $enable_ssl, insecure_noverify => $ssl_noverify, ca_path => $_ssl_cacert_path, cert_path => $_ssl_cert_path, key_path => $_ssl_key_path, } } # enable_ssl else { $attrs_ssl = { enable_tls => $enable_ssl } } - # The password parameter isn't parsed anymore. - if $password { - $_password = "-:\"${password}\"" + $_password = if $password =~ String { + Sensitive($password) + } elsif $password =~ Sensitive { + $password } else { - $_password = undef + undef } $attrs = { host => $host, port => $port, index => $index, username => $username, password => $_password, enable_send_perfdata => $enable_send_perfdata, flush_interval => $flush_interval, flush_threshold => $flush_threshold, enable_ha => $enable_ha, } # create object icinga2::object { 'icinga2::object::ElasticsearchWriter::elasticsearch': object_name => 'elasticsearch', object_type => 'ElasticsearchWriter', attrs => delete_undef_values(merge($attrs, $attrs_ssl)), attrs_list => keys($attrs), target => "${conf_dir}/features-available/elasticsearch.conf", notify => $_notify, order => 10, } # import library 'perfdata' concat::fragment { 'icinga2::feature::elasticsearch': target => "${conf_dir}/features-available/elasticsearch.conf", content => "library \"perfdata\"\n\n", order => '05', } icinga2::feature { 'elasticsearch': ensure => $ensure, } } diff --git a/manifests/feature/icingadb.pp b/manifests/feature/icingadb.pp index ff5e39a..db10e59 100644 --- a/manifests/feature/icingadb.pp +++ b/manifests/feature/icingadb.pp @@ -1,70 +1,73 @@ # @summary # Configures the Icinga 2 feature icingadb. # # @param [Enum['absent', 'present']] ensure # Set to present, enables the feature icingadb, absent disabled it. # # @param [Optional[Stdlib::Host]] host # IcingaDB Redis host address. # # @param [Optional[Stdlib::Port::Unprivileged]] port # IcingaDB Redis port. # # @param [Optional[Stdlib::Absolutepath]] socket_path # IcingaDB Redis unix sockt. Can be used instead of host and port attributes. # # @param [Optional[Icinga2::Interval]] connect_timeout # Timeout for establishing new connections. # -# @param [Optional[String]] password +# @param [Optional[Variant[String, Sensitive[String]]]] password # IcingaDB Redis password. The password parameter isn't parsed anymore. # class icinga2::feature::icingadb( - Enum['absent', 'present'] $ensure = present, - Optional[Stdlib::Host] $host = undef, - Optional[Stdlib::Port::Unprivileged] $port = undef, - Optional[Stdlib::Absolutepath] $socket_path = undef, - Optional[Icinga2::Interval] $connect_timeout = undef, - Optional[String] $password = undef, + Enum['absent', 'present'] $ensure = present, + Optional[Stdlib::Host] $host = undef, + Optional[Stdlib::Port::Unprivileged] $port = undef, + Optional[Stdlib::Absolutepath] $socket_path = undef, + Optional[Icinga2::Interval] $connect_timeout = undef, + Optional[Variant[String, Sensitive[String]]] $password = undef, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $conf_dir = $::icinga2::globals::conf_dir + $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } - # The password parameter isn't parsed anymore. - if $password { - $_password = "-:\"${password}\"" + + $_password = if $password =~ String { + Sensitive($password) + } elsif $password =~ Sensitive { + $password } else { - $_password = undef + undef } # compose attributes $attrs = { host => $host, port => $port, path => $socket_path, password => $_password, } # create object icinga2::object { 'icinga2::object::IcingaDB::icingadb': object_name => 'icingadb', object_type => 'IcingaDB', attrs => delete_undef_values($attrs), attrs_list => keys($attrs), target => "${conf_dir}/features-available/icingadb.conf", order => 10, notify => $_notify, } # manage feature icinga2::feature { 'icingadb': ensure => $ensure, } } diff --git a/manifests/feature/idomysql.pp b/manifests/feature/idomysql.pp index 106ec06..d57dee7 100644 --- a/manifests/feature/idomysql.pp +++ b/manifests/feature/idomysql.pp @@ -1,337 +1,343 @@ # @summary # Installs and configures the Icinga 2 feature ido-mysql. # # @example The ido-mysql featue requires an existing database and a user with permissions. This example uses the [puppetlabs/mysql](https://forge.puppet.com/puppetlabs/mysql) module. # include mysql::server # # mysql::db { 'icinga2': # user => 'icinga2', # password => 'supersecret', # host => 'localhost', # grant => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE VIEW', 'CREATE', 'INDEX', 'EXECUTE', 'ALTER'], # } # # class{ 'icinga2::feature::idomysql': # user => "icinga2", # password => "supersecret", # database => "icinga2", # import_schema => true, # require => Mysql::Db['icinga2'] # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature ido-mysql, absent disables it. # # @param [Stdlib::Host] host # MySQL database host address. # # @param [Optional[Stdlib::Port::Unprivileged]] port # MySQL database port. # # @param [Optional[Stdlib::Absolutepath]] socket_path # MySQL socket path. # # @param [String] user # MySQL database user with read/write permission to the icinga database. # -# @param [String] password +# @param [Variant[String, Sensitive[String]] password # MySQL database user's password. The password parameter isn't parsed anymore. # # @param [String] database # MySQL database name. # # @param [Boolean] enable_ssl # Either enable or disable SSL/TLS. Other SSL parameters are only affected if this is set to 'true'. # # @param [Optional[Stdlib::Absolutepath]] ssl_key_path # Location of the private key. Only valid if ssl is enabled. # # @param [Optional[Stdlib::Absolutepath]] ssl_cert_path # Location of the certificate. Only valid if ssl is enabled. # # @param [Optional[Stdlib::Absolutepath]] ssl_cacert_path # Location of the CA certificate. Only valid if ssl is enabled. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in spicified ssl_key_path file. # Only valid if ssl is enabled. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded string to store in spicified ssl_cert_path file. # Only valid if ssl is enabled. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded string to store in spicified ssl_cacert_path file. # Only valid if ssl is enabled. # # @param [Optional[Stdlib::Absolutepath]] ssl_capath # MySQL SSL trusted SSL CA certificates in PEM format directory path. Only valid if ssl is enabled. # # @param [Optional[String]] ssl_cipher # MySQL SSL list of allowed ciphers. Only valid if ssl is enabled. # # @param [Optional[String]] table_prefix # MySQL database table prefix. # # @param [Optional[String]] instance_name # Unique identifier for the local Icinga 2 instance. # # @param [Optional[String]] instance_description # Description for the Icinga 2 instance. # # @param [Optional[Boolean]] enable_ha # Enable the high availability functionality. Only valid in a cluster setup. # # @param [Optional[Icinga2::Interval]] failover_timeout # Set the failover timeout in a HA cluster. Must not be lower than 60s. # # @param [Optional[Icinga2::IdoCleanup]] cleanup # Hash with items for historical table cleanup. # # @param [Optional[Array]] categories # Array of information types that should be written to the database. # # @param [Boolean] import_schema # Whether to import the MySQL schema or not. # class icinga2::feature::idomysql( - String $password, + Variant[String, Sensitive[String]] $password, Enum['absent', 'present'] $ensure = present, Stdlib::Host $host = 'localhost', Optional[Stdlib::Port::Unprivileged] $port = undef, Optional[Stdlib::Absolutepath] $socket_path = undef, String $user = 'icinga', String $database = 'icinga', Boolean $enable_ssl = false, Optional[Stdlib::Absolutepath] $ssl_key_path = undef, Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, Optional[Stdlib::Base64] $ssl_key = undef, Optional[Stdlib::Base64] $ssl_cert = undef, Optional[Stdlib::Base64] $ssl_cacert = undef, Optional[Stdlib::Absolutepath] $ssl_capath = undef, Optional[String] $ssl_cipher = undef, Optional[String] $table_prefix = undef, Optional[String] $instance_name = undef, Optional[String] $instance_description = undef, Optional[Boolean] $enable_ha = undef, Optional[Icinga2::Interval] $failover_timeout = undef, Optional[Icinga2::IdoCleanup] $cleanup = undef, Optional[Array] $categories = undef, Boolean $import_schema = false, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $owner = $::icinga2::globals::user $group = $::icinga2::globals::group $conf_dir = $::icinga2::globals::conf_dir $ssl_dir = $::icinga2::globals::cert_dir $ido_mysql_package_name = $::icinga2::globals::ido_mysql_package_name $ido_mysql_schema = $::icinga2::globals::ido_mysql_schema $manage_package = $::icinga2::manage_package $manage_packages = $::icinga2::manage_packages $_ssl_key_mode = $::facts['os']['family'] ? { 'windows' => undef, default => '0600', } - $_notify = $ensure ? { + $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } + $_password = if $password =~ Sensitive { + $password + } else { + Sensitive($password) + } + # to build mysql exec command to import schema if $import_schema { $_mysql_options = join(any2array(delete_undef_values({ '-h' => $host ? { /localhost/ => undef, default => $host, }, '-P' => $port, '-u' => $user, })), ' ') } File { owner => $owner, group => $group, } if $enable_ssl { # Set defaults for certificate stuff if $ssl_key { if $ssl_key_path { $_ssl_key_path = $ssl_key_path } else { $_ssl_key_path = "${ssl_dir}/IdoMysqlConnection_ido-mysql.key" } $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $ssl_key, show_diff => false, tag => 'icinga2::config::file', } } else { $_ssl_key_path = $ssl_key_path } if $ssl_cert { if $ssl_cert_path { $_ssl_cert_path = $ssl_cert_path } else { $_ssl_cert_path = "${ssl_dir}/IdoMysqlConnection_ido-mysql.crt" } $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $ssl_cert, tag => 'icinga2::config::file', } } else { $_ssl_cert_path = $ssl_cert_path } if $ssl_cacert { if $ssl_cacert_path { $_ssl_cacert_path = $ssl_cacert_path } else { $_ssl_cacert_path = "${ssl_dir}/IdoMysqlConnection_ido-mysql_ca.crt" } $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $ssl_cacert, tag => 'icinga2::config::file', } } else { $_ssl_cacert_path = $ssl_cacert_path } if $import_schema { if $enable_ssl { $_ssl_options = join(any2array(delete_undef_values({ '--ssl' => '', '--ssl-ca' => $_ssl_cacert_path, '--ssl-cert' => $_ssl_cert_path, '--ssl-key' => $_ssl_key_path, '--ssl-capath' => $ssl_capath, '--ssl-cipher' => $ssl_cipher, })), ' ') } else { $_ssl_options = '' } # set cli options for mysql connection via tls - $_mysql_command = "mysql ${_mysql_options} -p'${password}' ${_ssl_options} ${database}" + $_mysql_command = "mysql ${_mysql_options} -p'${_password.unwrap}' ${_ssl_options} ${database}" } $attrs_ssl = { enable_ssl => $enable_ssl, ssl_ca => $_ssl_cacert_path, ssl_cert => $_ssl_cert_path, ssl_key => $_ssl_key_path, ssl_capath => $ssl_capath, ssl_cipher => $ssl_cipher, } } # enable_ssl else { # set cli options for mysql connection if $import_schema { - $_mysql_command = "mysql ${_mysql_options} -p'${password}' ${database}" } + $_mysql_command = "mysql ${_mysql_options} -p'${_password.unwrap}' ${database}" } $attrs_ssl = { enable_ssl => $enable_ssl } } $attrs = { host => $host, port => $port, socket_path => $socket_path, user => $user, - password => "-:\"${password}\"", # The password parameter isn't parsed anymore. + password => $_password, database => $database, table_prefix => $table_prefix, instance_name => $instance_name, instance_description => $instance_description, enable_ha => $enable_ha, failover_timeout => $failover_timeout, cleanup => $cleanup, categories => $categories, } # install additional package if $ido_mysql_package_name and ($manage_package or $manage_packages) { if $::facts['os']['family'] == 'debian' { ensure_resources('file', { '/etc/dbconfig-common' => { ensure => directory, owner => 'root', group => 'root' } }) file { "/etc/dbconfig-common/${ido_mysql_package_name}.conf": ensure => file, content => "dbc_install='false'\ndbc_upgrade='false'\ndbc_remove='false'\n", owner => 'root', group => 'root', mode => '0600', before => Package[$ido_mysql_package_name], } } # Debian package { $ido_mysql_package_name: ensure => installed, before => Icinga2::Feature['ido-mysql'], } } # import db schema if $import_schema { if $ido_mysql_package_name and ($manage_package or $manage_packages) { Package[$ido_mysql_package_name] -> Exec['idomysql-import-schema'] } exec { 'idomysql-import-schema': user => 'root', path => $::facts['path'], command => "${_mysql_command} < \"${ido_mysql_schema}\"", unless => "${_mysql_command} -Ns -e 'select version from icinga_dbversion'", } } # create object icinga2::object { 'icinga2::object::IdoMysqlConnection::ido-mysql': object_name => 'ido-mysql', object_type => 'IdoMysqlConnection', attrs => delete_undef_values(merge($attrs, $attrs_ssl)), attrs_list => concat(keys($attrs), keys($attrs_ssl)), target => "${conf_dir}/features-available/ido-mysql.conf", order => 10, notify => $_notify, } # import library concat::fragment { 'icinga2::feature::ido-mysql': target => "${conf_dir}/features-available/ido-mysql.conf", content => "library \"db_ido_mysql\"\n\n", order => '05', } icinga2::feature { 'ido-mysql': ensure => $ensure, } } diff --git a/manifests/feature/idopgsql.pp b/manifests/feature/idopgsql.pp index dab051a..2a6929e 100644 --- a/manifests/feature/idopgsql.pp +++ b/manifests/feature/idopgsql.pp @@ -1,290 +1,296 @@ # @summary # Installs and configures the Icinga 2 feature ido-pgsql. # # @example The ido-pgsql featue requires an existing database and a user with permissions. This example uses the [puppetlab/postgresql](https://forge.puppet.com/puppetlabs/postgresql) module. # include icinga2 # include postgresql::server # # postgresql::server::db { 'icinga2': # user => 'icinga2', -# password => postgresql_password('icinga2', 'supersecret'), +# password => postgresql::postgresql_password('icinga2', 'supersecret'), # } # # class{ 'icinga2::feature::idopgsql': # user => 'icinga2', # password => 'supersecret', # database => 'icinga2', # import_schema => true, # require => Postgresql::Server::Db['icinga2'] # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature ido-pgsql, absent disables it. # # @param [Stdlib::Host] host # PostgreSQL database host address. # # @param [Stdlib::Port::Unprivileged] port # PostgreSQL database port. # # @param [String] user # PostgreSQL database user with read/write permission to the icinga database. # -# @param [String] password +# @param Variant[[String, Sensitive[String]]] password # PostgreSQL database user's password. The password parameter isn't parsed anymore. # # @param [String] database # PostgreSQL database name. # # @param [Optional[Enum[disable', 'allow', 'prefer', 'verify-full', 'varify-ca', 'require']]] ssl_mode # Enable SSL connection mode. # # @param [Optional[Stdlib::Absolutepath]] ssl_key_path # Location of the private key. # # @param [Optional[Stdlib::Absolutepath]] ssl_cert_path # Location of the certificate. # # @param [Optional[Stdlib::Absolutepath]] ssl_cacert_path # Location of the CA certificate. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in spicified ssl_key_path file. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded string to store in spicified ssl_cert_path file. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded string to store in spicified ssl_cacert_path file. # # @param [Optional[String]] table_prefix # PostgreSQL database table prefix. # # @param [Optional[String]] instance_name # Unique identifier for the local Icinga 2 instance. # # @param [Optional[String]] instance_description # Description of the Icinga 2 instance. # # @param [Optional[Boolean]] enable_ha # Enable the high availability functionality. Only valid in a cluster setup. # # @param [Optional[Icinga2::Interval]] failover_timeout # Set the failover timeout in a HA cluster. Must not be lower than 60s. # # @param [Optional[Icinga2::IdoCleanup]] cleanup # Hash with items for historical table cleanup. # # @param [Optional[Array]] categories # Array of information types that should be written to the database. # # @param [Boolean] import_schema # Whether to import the PostgreSQL schema or not. # class icinga2::feature::idopgsql( - String $password, - Enum['absent', 'present'] $ensure = present, - Stdlib::Host $host = 'localhost', - Stdlib::Port::Unprivileged $port = 5432, - String $user = 'icinga', - String $database = 'icinga', + Variant[String, Sensitive[String]] $password, + Enum['absent', 'present'] $ensure = present, + Stdlib::Host $host = 'localhost', + Stdlib::Port::Unprivileged $port = 5432, + String $user = 'icinga', + String $database = 'icinga', Optional[Enum[ 'disable', 'allow', 'prefer', 'verify-full', - 'verify-ca', 'require']] $ssl_mode = undef, - Optional[Stdlib::Absolutepath] $ssl_key_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, - Optional[Stdlib::Base64] $ssl_key = undef, - Optional[Stdlib::Base64] $ssl_cert = undef, - Optional[Stdlib::Base64] $ssl_cacert = undef, - Optional[String] $table_prefix = undef, - Optional[String] $instance_name = undef, - Optional[String] $instance_description = undef, - Optional[Boolean] $enable_ha = undef, - Optional[Icinga2::Interval] $failover_timeout = undef, - Optional[Icinga2::IdoCleanup] $cleanup = undef, - Optional[Array] $categories = undef, - Boolean $import_schema = false, + 'verify-ca', 'require']] $ssl_mode = undef, + Optional[Stdlib::Absolutepath] $ssl_key_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, + Optional[Stdlib::Base64] $ssl_key = undef, + Optional[Stdlib::Base64] $ssl_cert = undef, + Optional[Stdlib::Base64] $ssl_cacert = undef, + Optional[String] $table_prefix = undef, + Optional[String] $instance_name = undef, + Optional[String] $instance_description = undef, + Optional[Boolean] $enable_ha = undef, + Optional[Icinga2::Interval] $failover_timeout = undef, + Optional[Icinga2::IdoCleanup] $cleanup = undef, + Optional[Array] $categories = undef, + Boolean $import_schema = false, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $owner = $::icinga2::globals::user $group = $::icinga2::globals::group $conf_dir = $::icinga2::globals::conf_dir $ssl_dir = $::icinga2::globals::cert_dir $ido_pgsql_package_name = $::icinga2::globals::ido_pgsql_package_name $ido_pgsql_schema = $::icinga2::globals::ido_pgsql_schema $manage_package = $::icinga2::manage_package $manage_packages = $::icinga2::manage_packages $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } $_ssl_key_mode = $::facts['os']['family'] ? { 'windows' => undef, default => '0600', } + $_password = if $password =~ Sensitive { + $password + } else { + Sensitive($password) + } + File { owner => $owner, group => $group, } # Set defaults for certificate stuff if $ssl_key { if $ssl_key_path { $_ssl_key_path = $ssl_key_path } else { $_ssl_key_path = "${ssl_dir}/IdoPgsqlConnection_ido-pgsql.key" } $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $ssl_key, show_diff => false, tag => 'icinga2::config::file', } } else { $_ssl_key_path = $ssl_key_path } if $ssl_cert { if $ssl_cert_path { $_ssl_cert_path = $ssl_cert_path } else { $_ssl_cert_path = "${ssl_dir}/IdoPgsqlConnection_ido-pgsql.crt" } $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $ssl_cert, tag => 'icinga2::config::file', } } else { $_ssl_cert_path = $ssl_cert_path } if $ssl_cacert { if $ssl_cacert_path { $_ssl_cacert_path = $ssl_cacert_path } else { $_ssl_cacert_path = "${ssl_dir}/IdoPgsqlConnection_ido-pgsql_ca.crt" } $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $ssl_cacert, tag => 'icinga2::config::file', } } else { $_ssl_cacert_path = $ssl_cacert_path } $attrs = { host => $host, port => $port, user => $user, - password => "-:\"${password}\"", # The password parameter isn't parsed anymore. + password => $_password, database => $database, ssl_mode => $ssl_mode, ssl_key => $_ssl_key_path, ssl_cert => $_ssl_cert_path, ssl_ca => $_ssl_cacert_path, table_prefix => $table_prefix, instance_name => $instance_name, instance_description => $instance_description, enable_ha => $enable_ha, failover_timeout => $failover_timeout, cleanup => $cleanup, categories => $categories, } # install additional package if $ido_pgsql_package_name and ($manage_package or $manage_packages) { if $::facts['os']['family'] == 'debian' { ensure_resources('file', { '/etc/dbconfig-common' => { ensure => directory, owner => 'root', group => 'root' } }) file { "/etc/dbconfig-common/${ido_pgsql_package_name}.conf": ensure => file, content => "dbc_install='false'\ndbc_upgrade='false'\ndbc_remove='false'\n", owner => 'root', group => 'root', mode => '0600', before => Package[$ido_pgsql_package_name], } } # Debian package { $ido_pgsql_package_name: ensure => installed, before => Icinga2::Feature['ido-pgsql'], } } # import db schema if $import_schema { if $ido_pgsql_package_name and ($manage_package or $manage_packages) { Package[$ido_pgsql_package_name] -> Exec['idopgsql-import-schema'] } $_connection = regsubst(join(any2array(delete_undef_values({ 'host=' => $host, 'sslmode=' => $ssl_mode, 'sslcert=' => $_ssl_cert_path, 'sslkey=' => $_ssl_key_path, 'sslrootcert=' => $_ssl_cacert_path, 'user=' => $user, 'port=' => $port, 'dbname=' => $database, })), ' '), '= ', '=', 'G') exec { 'idopgsql-import-schema': user => 'root', path => $::facts['path'], - environment => ["PGPASSWORD=${password}"], + environment => ["PGPASSWORD=${_password.unwrap}"], command => "psql '${_connection}' -w -f '${ido_pgsql_schema}'", unless => "psql '${_connection}' -w -c 'select version from icinga_dbversion'", } } # create object icinga2::object { 'icinga2::object::IdoPgsqlConnection::ido-pgsql': object_name => 'ido-pgsql', object_type => 'IdoPgsqlConnection', attrs => delete_undef_values($attrs), attrs_list => keys($attrs), target => "${conf_dir}/features-available/ido-pgsql.conf", order => 10, notify => $_notify, } # import library concat::fragment { 'icinga2::feature::ido-pgsql': target => "${conf_dir}/features-available/ido-pgsql.conf", content => "library \"db_ido_pgsql\"\n\n", order => '05', } icinga2::feature { 'ido-pgsql': ensure => $ensure, } } diff --git a/manifests/feature/influxdb.pp b/manifests/feature/influxdb.pp index 4ab63be..2509ea1 100644 --- a/manifests/feature/influxdb.pp +++ b/manifests/feature/influxdb.pp @@ -1,260 +1,274 @@ # @summary # Configures the Icinga 2 feature influxdb. # # @example # class { 'icinga2::feature::influxdb': # host => "10.10.0.15", # username => "icinga2", # password => "supersecret", # database => "icinga2" # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature influxdb, absent disables it. # # @param [Optional[Stdlib::Host]] host # InfluxDB host address. # # @param [Optional[Stdlib::Port]] port # InfluxDB HTTP port. # # @param [Optional[String]] database # InfluxDB database name. # # @param [Optional[String]] username # InfluxDB user name. # -# @param [Optional[String]] password +# @param [Optional[Variant[String, Sensitive[String]]]] password # InfluxDB user password. The password parameter isn't parsed anymore. # -# @param [Optional[Hash[Enum['username', 'password'], String]]] basic_auth +# @param [Optional[Icinga2::BasicAuth]] basic_auth # Username and password for HTTP basic authentication. # # @param [Optional[Boolean]] enable_ssl # Either enable or disable SSL. Other SSL parameters are only affected if this is set to 'true'. # # @param [Optional[Boolean]] ssl_noverify # Disable TLS peer verification. # # @param [Optional[Stdlib::Absolutepath]] ssl_key_path # Location of the private key. # # @param [Optional[Stdlib::Absolutepath]] ssl_cert_path # Location of the certificate. # # @param [Optional[Stdlib::Absolutepath]] ssl_cacert_path # Location of the CA certificate. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in ssl_key_path file. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded string to store in ssl_cert_path file. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded to store in ssl_cacert_path file. # # @param [String] host_measurement # The value of this is used for the measurement setting in host_template. # # @param [Hash] host_tags # Tags defined in this hash will be set in the host_template. # # @param [String] service_measurement # The value of this is used for the measurement setting in host_template. # # @param [Hash] service_tags # Tags defined in this hash will be set in the service_template. # # @param [Optional[Boolean]] enable_send_thresholds # Whether to send warn, crit, min & max tagged data. # # @param [Optional[Boolean]] enable_send_metadata # Whether to send check metadata e.g. states, execution time, latency etc. # # @param [Optional[Icinga2::Interval]] flush_interval # How long to buffer data points before transfering to InfluxDB. # # @param [Optional[Integer[1]]] flush_threshold # How many data points to buffer before forcing a transfer to InfluxDB. # # @param [Optional[Boolean]] enable_ha # Enable the high availability functionality. Only valid in a cluster setup. # class icinga2::feature::influxdb( - Enum['absent', 'present'] $ensure = present, - Optional[Stdlib::Host] $host = undef, - Optional[Stdlib::Port] $port = undef, - Optional[String] $database = undef, - Optional[String] $username = undef, - Optional[String] $password = undef, - Optional[Hash[Enum['username', 'password'], String]] $basic_auth = undef, - Optional[Boolean] $enable_ssl = undef, - Optional[Boolean] $ssl_noverify = undef, - Optional[Stdlib::Absolutepath] $ssl_key_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, - Optional[Stdlib::Base64] $ssl_key = undef, - Optional[Stdlib::Base64] $ssl_cert = undef, - Optional[Stdlib::Base64] $ssl_cacert = undef, - String $host_measurement = '$host.check_command$', - Hash $host_tags = { hostname => '$host.name$' }, - String $service_measurement = '$service.check_command$', - Hash $service_tags = { hostname => '$host.name$', service => '$service.name$' }, - Optional[Boolean] $enable_send_thresholds = undef, - Optional[Boolean] $enable_send_metadata = undef, - Optional[Icinga2::Interval] $flush_interval = undef, - Optional[Integer[1]] $flush_threshold = undef, - Optional[Boolean] $enable_ha = undef, + Enum['absent', 'present'] $ensure = present, + Optional[Stdlib::Host] $host = undef, + Optional[Stdlib::Port] $port = undef, + Optional[String] $database = undef, + Optional[String] $username = undef, + Optional[Variant[String, Sensitive[String]]] $password = undef, + Optional[Icinga2::BasicAuth] $basic_auth = undef, + Optional[Boolean] $enable_ssl = undef, + Optional[Boolean] $ssl_noverify = undef, + Optional[Stdlib::Absolutepath] $ssl_key_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, + Optional[Stdlib::Base64] $ssl_key = undef, + Optional[Stdlib::Base64] $ssl_cert = undef, + Optional[Stdlib::Base64] $ssl_cacert = undef, + String $host_measurement = '$host.check_command$', + Hash $host_tags = { hostname => '$host.name$' }, + String $service_measurement = '$service.check_command$', + Hash $service_tags = { hostname => '$host.name$', service => '$service.name$' }, + Optional[Boolean] $enable_send_thresholds = undef, + Optional[Boolean] $enable_send_metadata = undef, + Optional[Icinga2::Interval] $flush_interval = undef, + Optional[Integer[1]] $flush_threshold = undef, + Optional[Boolean] $enable_ha = undef, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $user = $::icinga2::globals::user $group = $::icinga2::globals::group $conf_dir = $::icinga2::globals::conf_dir $ssl_dir = $::icinga2::globals::cert_dir + $_ssl_key_mode = $::facts['kernel'] ? { 'windows' => undef, default => '0600', } + $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } + if $basic_auth { + $_basic_auth = if $basic_auth['password'] =~ String { + $basic_auth + { 'password' => Sensitive($password) } + } elsif $basic_auth['password'] =~ Sensitive { + $basic_auth + } + } else { + $_basic_auth = undef + } + File { owner => $user, group => $group, } $host_template = { measurement => $host_measurement, tags => $host_tags } $service_template = { measurement => $service_measurement, tags => $service_tags} if $enable_ssl { # Set defaults for certificate stuff if $ssl_key { if $ssl_key_path { $_ssl_key_path = $ssl_key_path } else { $_ssl_key_path = "${ssl_dir}/InfluxdbWriter_influxdb.key" } $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $_ssl_key, show_diff => false, tag => 'icinga2::config::file', } } else { $_ssl_key_path = $ssl_key_path } if $ssl_cert { if $ssl_cert_path { $_ssl_cert_path = $ssl_cert_path } else { $_ssl_cert_path = "${ssl_dir}/InfluxdbWriter_influxdb.crt" } $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $_ssl_cert, tag => 'icinga2::config::file', } } else { $_ssl_cert_path = $ssl_cert_path } if $ssl_cacert { if $ssl_cacert_path { $_ssl_cacert_path = $ssl_cacert_path } else { $_ssl_cacert_path = "${ssl_dir}/InfluxdbWriter_influxdb_ca.crt" } $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $_ssl_cacert, tag => 'icinga2::config::file', } } else { $_ssl_cacert_path = $ssl_cacert_path } $attrs_ssl = { ssl_enable => $enable_ssl, ssl_insecure_noverify => $ssl_noverify, ssl_ca_cert => $_ssl_cacert_path, ssl_cert => $_ssl_cert_path, ssl_key => $_ssl_key_path, } } # enable_ssl else { $attrs_ssl = { ssl_enable => $enable_ssl } } - # The password parameter isn't parsed anymore. - if $password { - $_password = "-:\"${password}\"" + $_password = if $password =~ String { + # The password parameter isn't parsed anymore. + "-:\"${password}\"" + } elsif $password =~ Sensitive { + $password } else { - $_password = undef + undef } $attrs = { host => $host, port => $port, database => $database, username => $username, password => $_password, - basic_auth => $basic_auth, + basic_auth => $_basic_auth, host_template => $host_template, service_template => $service_template, enable_send_thresholds => $enable_send_thresholds, enable_send_metadata => $enable_send_metadata, flush_interval => $flush_interval, flush_threshold => $flush_threshold, enable_ha => $enable_ha, } # create object icinga2::object { 'icinga2::object::InfluxdbWriter::influxdb': object_name => 'influxdb', object_type => 'InfluxdbWriter', attrs => delete_undef_values(merge($attrs, $attrs_ssl)), attrs_list => keys($attrs), target => "${conf_dir}/features-available/influxdb.conf", notify => $_notify, order => 10, } # import library 'perfdata' concat::fragment { 'icinga2::feature::influxdb': target => "${conf_dir}/features-available/influxdb.conf", content => "library \"perfdata\"\n\n", order => '05', } icinga2::feature { 'influxdb': ensure => $ensure, } } diff --git a/manifests/feature/influxdb2.pp b/manifests/feature/influxdb2.pp index 85fd443..0036bfe 100644 --- a/manifests/feature/influxdb2.pp +++ b/manifests/feature/influxdb2.pp @@ -1,241 +1,249 @@ # @summary # Configures the Icinga 2 feature influxdb2. # # @example # class { 'icinga2::feature::influxdb2': # host => "10.10.0.15", # organization => "ICINGA", # bucket => "icinga2", # auth_token => "supersecret", # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the feature influxdb, absent disables it. # # @param [Optional[Stdlib::Host]] host # InfluxDB host address. # # @param [Optional[Stdlib::Port]] port # InfluxDB HTTP port. # # @param [String] organization # InfluxDB organization name. # # @param [String] bucket # InfluxDB bucket name. # -# @param [String] auth_token +# @param [Variant[String, Sensitive[String]]] auth_token # InfluxDB authentication token. # # @param [Optional[Boolean]] enable_ssl # Either enable or disable SSL. Other SSL parameters are only affected if this is set to 'true'. # # @param [Optional[Boolean]] ssl_noverify # Disable TLS peer verification. # # @param [Optional[Stdlib::Absolutepath]] ssl_key_path # Location of the private key. # # @param [Optional[Stdlib::Absolutepath]] ssl_cert_path # Location of the certificate. # # @param [Optional[Stdlib::Absolutepath]] ssl_cacert_path # Location of the CA certificate. # # @param [Optional[Stdlib::Base64]] ssl_key # The private key in a base64 encoded string to store in ssl_key_path file. # # @param [Optional[Stdlib::Base64]] ssl_cert # The certificate in a base64 encoded string to store in ssl_cert_path file. # # @param [Optional[Stdlib::Base64]] ssl_cacert # The CA root certificate in a base64 encoded to store in ssl_cacert_path file. # # @param [String] host_measurement # The value of this is used for the measurement setting in host_template. # # @param [Hash] host_tags # Tags defined in this hash will be set in the host_template. # # @param [String] service_measurement # The value of this is used for the measurement setting in host_template. # # @param [Hash] service_tags # Tags defined in this hash will be set in the service_template. # # @param [Optional[Boolean]] enable_send_thresholds # Whether to send warn, crit, min & max tagged data. # # @param [Optional[Boolean]] enable_send_metadata # Whether to send check metadata e.g. states, execution time, latency etc. # # @param [Optional[Icinga2::Interval]] flush_interval # How long to buffer data points before transfering to InfluxDB. # # @param [Optional[Integer[1]]] flush_threshold # How many data points to buffer before forcing a transfer to InfluxDB. # # @param [Optional[Boolean]] enable_ha # Enable the high availability functionality. Only valid in a cluster setup. # class icinga2::feature::influxdb2( - String $organization, - String $bucket, - String $auth_token, - Enum['absent', 'present'] $ensure = present, - Optional[Stdlib::Host] $host = undef, - Optional[Stdlib::Port] $port = undef, - Optional[Boolean] $enable_ssl = undef, - Optional[Boolean] $ssl_noverify = undef, - Optional[Stdlib::Absolutepath] $ssl_key_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, - Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, - Optional[Stdlib::Base64] $ssl_key = undef, - Optional[Stdlib::Base64] $ssl_cert = undef, - Optional[Stdlib::Base64] $ssl_cacert = undef, - String $host_measurement = '$host.check_command$', - Hash $host_tags = { hostname => '$host.name$' }, - String $service_measurement = '$service.check_command$', - Hash $service_tags = { hostname => '$host.name$', service => '$service.name$' }, - Optional[Boolean] $enable_send_thresholds = undef, - Optional[Boolean] $enable_send_metadata = undef, - Optional[Icinga2::Interval] $flush_interval = undef, - Optional[Integer[1]] $flush_threshold = undef, - Optional[Boolean] $enable_ha = undef, + String $organization, + String $bucket, + Variant[String, Sensitive[String]] $auth_token, + Enum['absent', 'present'] $ensure = present, + Optional[Stdlib::Host] $host = undef, + Optional[Stdlib::Port] $port = undef, + Optional[Boolean] $enable_ssl = undef, + Optional[Boolean] $ssl_noverify = undef, + Optional[Stdlib::Absolutepath] $ssl_key_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cert_path = undef, + Optional[Stdlib::Absolutepath] $ssl_cacert_path = undef, + Optional[Stdlib::Base64] $ssl_key = undef, + Optional[Stdlib::Base64] $ssl_cert = undef, + Optional[Stdlib::Base64] $ssl_cacert = undef, + String $host_measurement = '$host.check_command$', + Hash $host_tags = { hostname => '$host.name$' }, + String $service_measurement = '$service.check_command$', + Hash $service_tags = { hostname => '$host.name$', service => '$service.name$' }, + Optional[Boolean] $enable_send_thresholds = undef, + Optional[Boolean] $enable_send_metadata = undef, + Optional[Icinga2::Interval] $flush_interval = undef, + Optional[Integer[1]] $flush_threshold = undef, + Optional[Boolean] $enable_ha = undef, ) { if ! defined(Class['::icinga2']) { fail('You must include the icinga2 base class before using any icinga2 feature class!') } $user = $::icinga2::globals::user $group = $::icinga2::globals::group $conf_dir = $::icinga2::globals::conf_dir $ssl_dir = $::icinga2::globals::cert_dir + $_ssl_key_mode = $::facts['kernel'] ? { 'windows' => undef, default => '0600', } + $_notify = $ensure ? { 'present' => Class['::icinga2::service'], default => undef, } + $_auth_token = if $auth_token =~ Sensitive { + $auth_token + } else { + Sensitive($auth_token) + } + File { owner => $user, group => $group, } $host_template = { measurement => $host_measurement, tags => $host_tags } $service_template = { measurement => $service_measurement, tags => $service_tags} if $enable_ssl { # Set defaults for certificate stuff if $ssl_key { if $ssl_key_path { $_ssl_key_path = $ssl_key_path } else { $_ssl_key_path = "${ssl_dir}/Influxdb2Writer_influxdb2.key" } $_ssl_key = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_key, '\n', "\r\n", 'EMG'), default => $ssl_key, } file { $_ssl_key_path: ensure => file, mode => $_ssl_key_mode, content => $_ssl_key, show_diff => false, tag => 'icinga2::config::file', } } else { $_ssl_key_path = $ssl_key_path } if $ssl_cert { if $ssl_cert_path { $_ssl_cert_path = $ssl_cert_path } else { $_ssl_cert_path = "${ssl_dir}/Influxdb2Writer_influxdb2.crt" } $_ssl_cert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cert, '\n', "\r\n", 'EMG'), default => $ssl_cert, } file { $_ssl_cert_path: ensure => file, content => $_ssl_cert, tag => 'icinga2::config::file', } } else { $_ssl_cert_path = $ssl_cert_path } if $ssl_cacert { if $ssl_cacert_path { $_ssl_cacert_path = $ssl_cacert_path } else { $_ssl_cacert_path = "${ssl_dir}/Influxdb2Writer_influxdb2_ca.crt" } $_ssl_cacert = $::facts['os']['family'] ? { 'windows' => regsubst($ssl_cacert, '\n', "\r\n", 'EMG'), default => $ssl_cacert, } file { $_ssl_cacert_path: ensure => file, content => $_ssl_cacert, tag => 'icinga2::config::file', } } else { $_ssl_cacert_path = $ssl_cacert_path } $attrs_ssl = { ssl_enable => $enable_ssl, ssl_insecure_noverify => $ssl_noverify, ssl_ca_cert => $_ssl_cacert_path, ssl_cert => $_ssl_cert_path, ssl_key => $_ssl_key_path, } } # enable_ssl else { $attrs_ssl = { ssl_enable => $enable_ssl } } $attrs = { host => $host, port => $port, organization => $organization, bucket => $bucket, - auth_token => "-:\"${auth_token}\"", + auth_token => $_auth_token, host_template => $host_template, service_template => $service_template, enable_send_thresholds => $enable_send_thresholds, enable_send_metadata => $enable_send_metadata, flush_interval => $flush_interval, flush_threshold => $flush_threshold, enable_ha => $enable_ha, } # create object icinga2::object { 'icinga2::object::Influxdb2Writer::influxdb2': object_name => 'influxdb2', object_type => 'Influxdb2Writer', attrs => delete_undef_values(merge($attrs, $attrs_ssl)), attrs_list => keys($attrs), target => "${conf_dir}/features-available/influxdb2.conf", notify => $_notify, order => 10, } icinga2::feature { 'influxdb2': ensure => $ensure, } } diff --git a/manifests/object/apiuser.pp b/manifests/object/apiuser.pp index 0c4df17..d2aef4c 100644 --- a/manifests/object/apiuser.pp +++ b/manifests/object/apiuser.pp @@ -1,94 +1,95 @@ # @summary # Manage Icinga 2 ApiUser objects. # # @example Create an user with full permissions: # ::icinga2::object::apiuser { 'director': # ensure => present, # password => 'Eih5Weefoo2oa8sh', # permissions => [ '*' ], # target => '/etc/icinga2/conf.d/api-users.conf', # } # # @example Create an user with restricted permissions for Icinga Web 2: # ::icinga2::object::apiuser { 'icingaweb2': # ensure => present, # password => '12e2ef553068b519', # permissions => [ 'status/query', 'actions/*', 'objects/modify/*', 'objects/query/*' ], # target => '/etc/icinga2/conf.d/api-users.conf', # } # # @example Create user who's only allowed to query hosts and services: # ::icinga2::object::apiuser { 'read': # ensure => present, # password => 'read', # permissions => [ # { # permission => 'objects/query/Host', # filter => '{{ regex("^Linux", host.vars.os) }}' # }, # { # permission => 'objects/query/Service', # filter => '{{ regex("^Linux", host.vars.os) }}' # }, # ], # target => '/etc/icinga2/conf.d/api-users.conf', # } # # @param [Enum['absent', 'present']] ensure # Set to present enables the object, absent disables it. # # @param [String] apiuser_name # Set the name of the apiuser object. # -# @param [Optional[String]] password +# @param [Optional[Variant[Stringi, Sensitive[String]]]] password # Password string. The password parameter isn't parsed anymore. # # @param [Optional[String]] client_cn # Optional. Client Common Name (CN). # # @param [Optional[Array]] permissions # Array of permissions. Either as string or dictionary with the keys permission # and filter. The latter must be specified as function. # # @param [Stdlib::Absolutepath] target # Destination config file to store in this object. File will be declared at the # first time. # # @param [Variant[String, Integer]] order # String or integer to set the position in the target file, sorted alpha numeric. # define icinga2::object::apiuser( - Stdlib::Absolutepath $target, - Enum['absent', 'present'] $ensure = present, - String $apiuser_name = $title, - Optional[Array] $permissions = undef, - Optional[String] $password = undef, - Optional[String] $client_cn = undef, - Variant[String, Integer] $order = 30, + Stdlib::Absolutepath $target, + Enum['absent', 'present'] $ensure = present, + String $apiuser_name = $title, + Optional[Array] $permissions = undef, + Optional[Variant[String, Sensitive[String]]] $password = undef, + Optional[String] $client_cn = undef, + Variant[String, Integer] $order = 30, ) { - # The password parameter isn't parsed anymore. - if $password { - $_password = "-:\"${password}\"" + $_password = if $password =~ String { + Sensitive($password) + } elsif $password =~ Sensitive { + $password } else { - $_password = undef + undef } # compose the attributes $attrs = { password => $_password, client_cn => $client_cn, permissions => $permissions, } # create object icinga2::object { "icinga2::object::ApiUser::${title}": ensure => $ensure, object_name => $apiuser_name, object_type => 'ApiUser', attrs => delete_undef_values($attrs), attrs_list => keys($attrs), target => $target, order => $order, } } diff --git a/types/basicauth.pp b/types/basicauth.pp new file mode 100644 index 0000000..f0b1683 --- /dev/null +++ b/types/basicauth.pp @@ -0,0 +1,5 @@ +# A strict type for basic authentication +type Icinga2::BasicAuth = Struct[{ + 'username' => String, + 'password' => Variant[String, Sensitive[String]], +}]