diff --git a/.puppet-lint.rc b/.puppet-lint.rc index cc96ece..a79ce0f 100644 --- a/.puppet-lint.rc +++ b/.puppet-lint.rc @@ -1 +1,2 @@ --relative +--no-140chars-check diff --git a/lib/puppet/functions/mysql/password.rb b/lib/puppet/functions/mysql/password.rb index 8cb9e55..caf9e81 100644 --- a/lib/puppet/functions/mysql/password.rb +++ b/lib/puppet/functions/mysql/password.rb @@ -1,24 +1,41 @@ # frozen_string_literal: true require 'digest/sha1' # @summary # Hash a string as mysql's "PASSWORD()" function would do it # Puppet::Functions.create_function(:'mysql::password') do # @param password # Plain text password. + # @param sensitive + # If the Postgresql-Passwordhash should be of Datatype Sensitive[String] # # @return hash # The mysql password hash from the clear text password. # dispatch :password do - required_param 'String', :password - return_type 'String' + required_param 'Variant[String, Sensitive[String]]', :password + optional_param 'Boolean', :sensitive + return_type 'Variant[String, Sensitive[String]]' end - def password(password) - return '' if password.empty? - return password if %r{\*[A-F0-9]{40}$}.match?(password) - '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(password)).upcase + def password(password, sensitive = false) + if password.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + password = password.unwrap + end + + result_string = if %r{\*[A-F0-9]{40}$}.match?(password) + password + elsif password.empty? + '' + else + '*' + Digest::SHA1.hexdigest(Digest::SHA1.digest(password)).upcase + end + + if sensitive + Puppet::Pops::Types::PSensitiveType::Sensitive.new(result_string) + else + result_string + end end end diff --git a/lib/puppet/functions/mysql_password.rb b/lib/puppet/functions/mysql_password.rb index 2353f04..89cf313 100644 --- a/lib/puppet/functions/mysql_password.rb +++ b/lib/puppet/functions/mysql_password.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true # @summary DEPRECATED. Use the namespaced function [`mysql::password`](#mysqlpassword) instead. Puppet::Functions.create_function(:mysql_password) do # @param password # Plain text password. # # @return # The mysql password hash from the 4.x function mysql::password. dispatch :mysql_password do - required_param 'String', :password - return_type 'String' + required_param 'Variant[String, Sensitive[String]]', :password + optional_param 'Boolean', :sensitive + return_type 'Variant[String, Sensitive[String]]' end - def mysql_password(password) + def mysql_password(password, sensitive = false) call_function('deprecation', 'mysql_password', "This method has been deprecated, please use the namespaced version 'mysql::password' instead.") - call_function('mysql::password', password) + call_function('mysql::password', password, sensitive) end end diff --git a/lib/puppet/provider/mysql_user/mysql.rb b/lib/puppet/provider/mysql_user/mysql.rb index 30d0dd2..f9efaa2 100644 --- a/lib/puppet/provider/mysql_user/mysql.rb +++ b/lib/puppet/provider/mysql_user/mysql.rb @@ -1,257 +1,259 @@ # frozen_string_literal: true require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_user).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'manage users for a mysql database.' commands mysql_raw: 'mysql' # Build a property_hash containing all the discovered information about MySQL # users. def self.instances users = mysql_caller("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').split("\n") # To reduce the number of calls to MySQL we collect all the properties in # one big swoop. users.map do |name| if mysqld_version.nil? ## Default ... # rubocop:disable Layout/LineLength query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, AUTHENTICATION_STRING, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" elsif newer_than('mariadb' => '10.1.21') query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD, PLUGIN, AUTHENTICATION_STRING FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" else query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT, PASSWORD /*!50508 , PLUGIN */ FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" end @max_user_connections, @max_connections_per_hour, @max_queries_per_hour, @max_updates_per_hour, ssl_type, ssl_cipher, x509_issuer, x509_subject, @password, @plugin, @authentication_string = mysql_caller(query, 'regular').chomp.split(%r{\t}) @tls_options = parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) if newer_than('mariadb' => '10.1.21') && @plugin == 'ed25519' # Some auth plugins (e.g. ed25519) use authentication_string # to store password hash or auth information @password = @authentication_string elsif (newer_than('mariadb' => '10.2.16') && older_than('mariadb' => '10.2.19')) || (newer_than('mariadb' => '10.3.8') && older_than('mariadb' => '10.3.11')) # Old mariadb 10.2 or 10.3 store password hash in authentication_string # https://jira.mariadb.org/browse/MDEV-16238 https://jira.mariadb.org/browse/MDEV-16774 @password = @authentication_string end # rubocop:enable Layout/LineLength new(name: name, ensure: :present, password_hash: @password, plugin: @plugin, max_user_connections: @max_user_connections, max_connections_per_hour: @max_connections_per_hour, max_queries_per_hour: @max_queries_per_hour, max_updates_per_hour: @max_updates_per_hour, tls_options: @tls_options) end end # We iterate over each mysql_user entry in the catalog and compare it against # the contents of the property_hash generated by self.instances def self.prefetch(resources) users = instances # rubocop:disable Lint/AssignmentInCondition resources.each_key do |name| if provider = users.find { |user| user.name == name } resources[name].provider = provider end end # rubocop:enable Lint/AssignmentInCondition end def create # (MODULES-3539) Allow @ in username merged_name = @resource[:name].reverse.sub('@', "'@'").reverse password_hash = @resource.value(:password_hash) plugin = @resource.value(:plugin) max_user_connections = @resource.value(:max_user_connections) || 0 max_connections_per_hour = @resource.value(:max_connections_per_hour) || 0 max_queries_per_hour = @resource.value(:max_queries_per_hour) || 0 max_updates_per_hour = @resource.value(:max_updates_per_hour) || 0 tls_options = @resource.value(:tls_options) || ['NONE'] + password_hash = password_hash.unwrap if password_hash.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) + # Use CREATE USER to be compatible with NO_AUTO_CREATE_USER sql_mode # This is also required if you want to specify a authentication plugin if !plugin.nil? if !password_hash.nil? self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}' AS '#{password_hash}'", 'system') else self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}'", 'system') end @property_hash[:ensure] = :present @property_hash[:plugin] = plugin elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.1.3') self.class.mysql_caller("CREATE USER IF NOT EXISTS '#{merged_name}' IDENTIFIED WITH 'mysql_native_password' AS '#{password_hash}'", 'system') @property_hash[:ensure] = :present @property_hash[:password_hash] = password_hash else self.class.mysql_caller("CREATE USER '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}'", 'system') @property_hash[:ensure] = :present @property_hash[:password_hash] = password_hash end # rubocop:disable Layout/LineLength if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') self.class.mysql_caller("ALTER USER IF EXISTS '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO '#{merged_name}' WITH MAX_USER_CONNECTIONS #{max_user_connections} MAX_CONNECTIONS_PER_HOUR #{max_connections_per_hour} MAX_QUERIES_PER_HOUR #{max_queries_per_hour} MAX_UPDATES_PER_HOUR #{max_updates_per_hour}", 'system') end # rubocop:enable Layout/LineLength @property_hash[:max_user_connections] = max_user_connections @property_hash[:max_connections_per_hour] = max_connections_per_hour @property_hash[:max_queries_per_hour] = max_queries_per_hour @property_hash[:max_updates_per_hour] = max_updates_per_hour merged_tls_options = tls_options.join(' AND ') if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') self.class.mysql_caller("ALTER USER '#{merged_name}' REQUIRE #{merged_tls_options}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO '#{merged_name}' REQUIRE #{merged_tls_options}", 'system') end @property_hash[:tls_options] = tls_options exists? ? (return true) : (return false) end def destroy # (MODULES-3539) Allow @ in username merged_name = @resource[:name].reverse.sub('@', "'@'").reverse if_exists = if newer_than('mysql' => '5.7', 'percona' => '5.7', 'mariadb' => '10.1.3') 'IF EXISTS ' else '' end self.class.mysql_caller("DROP USER #{if_exists}'#{merged_name}'", 'system') @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end ## ## MySQL user properties ## # Generates method for all properties of the property_hash mk_resource_methods def password_hash=(string) merged_name = self.class.cmd_user(@resource[:name]) plugin = @resource.value(:plugin) # We have a fact for the mysql version ... if mysqld_version.nil? # default ... if mysqld_version does not work self.class.mysql_caller("SET PASSWORD FOR #{merged_name} = '#{string}'", 'system') elsif newer_than('mariadb' => '10.1.21') && plugin == 'ed25519' raise ArgumentError, _('ed25519 hash should be 43 bytes long.') unless string.length == 43 # ALTER USER statement is only available upstream starting 10.2 # https://mariadb.com/kb/en/mariadb-1020-release-notes/ if newer_than('mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH ed25519 AS '#{string}'" else concat_name = @resource[:name] sql = "UPDATE mysql.user SET password = '', plugin = 'ed25519'" sql += ", authentication_string = '#{string}'" sql += " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" end self.class.mysql_caller(sql, 'system') elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') raise ArgumentError, _('Only mysql_native_password (*ABCD...XXX) hashes are supported.') unless %r{^\*|^$}.match?(string) self.class.mysql_caller("ALTER USER #{merged_name} IDENTIFIED WITH mysql_native_password AS '#{string}'", 'system') else self.class.mysql_caller("SET PASSWORD FOR #{merged_name} = '#{string}'", 'system') end (password_hash == string) ? (return true) : (return false) end def max_user_connections=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_USER_CONNECTIONS #{int}", 'system').chomp (max_user_connections == int) ? (return true) : (return false) end def max_connections_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_CONNECTIONS_PER_HOUR #{int}", 'system').chomp (max_connections_per_hour == int) ? (return true) : (return false) end def max_queries_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_QUERIES_PER_HOUR #{int}", 'system').chomp (max_queries_per_hour == int) ? (return true) : (return false) end def max_updates_per_hour=(int) merged_name = self.class.cmd_user(@resource[:name]) self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} WITH MAX_UPDATES_PER_HOUR #{int}", 'system').chomp (max_updates_per_hour == int) ? (return true) : (return false) end def plugin=(string) merged_name = self.class.cmd_user(@resource[:name]) if newer_than('mariadb' => '10.1.21') && string == 'ed25519' if newer_than('mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}' AS '#{@resource[:password_hash]}'" else concat_name = @resource[:name] sql = "UPDATE mysql.user SET password = '', plugin = '#{string}'" sql += ", authentication_string = '#{@resource[:password_hash]}'" sql += " where CONCAT(user, '@', host) = '#{concat_name}'; FLUSH PRIVILEGES" end elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}'" sql += " AS '#{@resource[:password_hash]}'" if string == 'mysql_native_password' else # See https://bugs.mysql.com/bug.php?id=67449 sql = "UPDATE mysql.user SET plugin = '#{string}'" sql += ((string == 'mysql_native_password') ? ", password = '#{@resource[:password_hash]}'" : ", password = ''") sql += " WHERE CONCAT(user, '@', host) = '#{@resource[:name]}'" end self.class.mysql_caller(sql, 'system') (plugin == string) ? (return true) : (return false) end def tls_options=(array) merged_name = self.class.cmd_user(@resource[:name]) merged_tls_options = array.join(' AND ') if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6', 'mariadb' => '10.2.0') self.class.mysql_caller("ALTER USER #{merged_name} REQUIRE #{merged_tls_options}", 'system') else self.class.mysql_caller("GRANT USAGE ON *.* TO #{merged_name} REQUIRE #{merged_tls_options}", 'system') end (tls_options == array) ? (return true) : (return false) end def self.parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject) if ssl_type == 'ANY' ['SSL'] elsif ssl_type == 'X509' ['X509'] elsif ssl_type == 'SPECIFIED' options = [] options << "CIPHER '#{ssl_cipher}'" if !ssl_cipher.nil? && !ssl_cipher.empty? options << "ISSUER '#{x509_issuer}'" if !x509_issuer.nil? && !x509_issuer.empty? options << "SUBJECT '#{x509_subject}'" if !x509_subject.nil? && !x509_subject.empty? options else ['NONE'] end end end diff --git a/manifests/backup/mysqlbackup.pp b/manifests/backup/mysqlbackup.pp index b671396..c5eefbc 100644 --- a/manifests/backup/mysqlbackup.pp +++ b/manifests/backup/mysqlbackup.pp @@ -1,124 +1,129 @@ # @summary # Manage the mysqlbackup client. # # @api private # class mysql::backup::mysqlbackup ( $backupuser = '', - $backuppassword = '', + Variant[String, Sensitive[String]] $backuppassword = '', $maxallowedpacket = '1M', $backupdir = '', $backupdirmode = '0700', $backupdirowner = 'root', $backupdirgroup = $mysql::params::root_group, $backupcompress = true, $backuprotate = 30, $backupmethod = '', $backup_success_file_path = undef, $ignore_events = true, $delete_before_dump = false, $backupdatabases = [], $file_per_database = false, $include_triggers = true, $include_routines = false, $ensure = 'present', $time = ['23', '5'], $prescript = false, $postscript = false, $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', $optional_args = [], $incremental_backups = false, $install_cron = true, $compression_command = undef, $compression_extension = undef, ) inherits mysql::params { + $backuppassword_unsensitive = if $backuppassword =~ Sensitive { + $backuppassword.unwrap + } else { + $backuppassword + } mysql_user { "${backupuser}@localhost": ensure => $ensure, password_hash => mysql::password($backuppassword), require => Class['mysql::server::root_password'], } package { 'meb': ensure => $ensure, } # http://dev.mysql.com/doc/mysql-enterprise-backup/3.11/en/mysqlbackup.privileges.html mysql_grant { "${backupuser}@localhost/*.*": ensure => $ensure, user => "${backupuser}@localhost", table => '*.*', privileges => ['RELOAD', 'SUPER', 'REPLICATION CLIENT'], require => Mysql_user["${backupuser}@localhost"], } mysql_grant { "${backupuser}@localhost/mysql.backup_progress": ensure => $ensure, user => "${backupuser}@localhost", table => 'mysql.backup_progress', privileges => ['CREATE', 'INSERT', 'DROP', 'UPDATE'], require => Mysql_user["${backupuser}@localhost"], } mysql_grant { "${backupuser}@localhost/mysql.backup_history": ensure => $ensure, user => "${backupuser}@localhost", table => 'mysql.backup_history', privileges => ['CREATE', 'INSERT', 'SELECT', 'DROP', 'UPDATE'], require => Mysql_user["${backupuser}@localhost"], } if $install_cron { if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '5' { ensure_packages('crontabs') } elsif $::osfamily == 'RedHat' { ensure_packages('cronie') } elsif $::osfamily != 'FreeBSD' { ensure_packages('cron') } } cron { 'mysqlbackup-weekly': ensure => $ensure, command => 'mysqlbackup backup', user => 'root', hour => $time[0], minute => $time[1], weekday => '0', require => Package['meb'], } cron { 'mysqlbackup-daily': ensure => $ensure, command => 'mysqlbackup --incremental backup', user => 'root', hour => $time[0], minute => $time[1], weekday => '1-6', require => Package['meb'], } $default_options = { 'mysqlbackup' => { 'backup-dir' => $backupdir, 'with-timestamp' => true, 'incremental_base' => 'history:last_backup', 'incremental_backup_dir' => $backupdir, 'user' => $backupuser, - 'password' => $backuppassword, + 'password' => $backuppassword_unsensitive }, } $options = mysql::normalise_and_deepmerge($default_options, $mysql::server::override_options) file { 'mysqlbackup-config-file': path => '/etc/mysql/conf.d/meb.cnf', content => template('mysql/meb.cnf.erb'), mode => '0600', } file { $backupdir: ensure => 'directory', mode => $backupdirmode, owner => $backupdirowner, group => $backupdirgroup, } } diff --git a/manifests/backup/mysqldump.pp b/manifests/backup/mysqldump.pp index 29c68d3..a259752 100644 --- a/manifests/backup/mysqldump.pp +++ b/manifests/backup/mysqldump.pp @@ -1,110 +1,117 @@ # @summary # "Provider" for mysqldump # @api private # class mysql::backup::mysqldump ( $backupuser = '', - $backuppassword = '', + Variant[String, Sensitive[String]] $backuppassword = '', $backupdir = '', $maxallowedpacket = '1M', $backupdirmode = '0700', $backupdirowner = 'root', $backupdirgroup = $mysql::params::root_group, $backupcompress = true, $backuprotate = 30, $backupmethod = 'mysqldump', $backup_success_file_path = undef, $ignore_events = true, $delete_before_dump = false, $backupdatabases = [], $file_per_database = false, $include_triggers = false, $include_routines = false, $ensure = 'present', $time = ['23', '5'], $prescript = false, $postscript = false, $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', $optional_args = [], $mysqlbackupdir_ensure = 'directory', $mysqlbackupdir_target = undef, $incremental_backups = false, $install_cron = true, $compression_command = 'bzcat -zc', $compression_extension = '.bz2' ) inherits mysql::params { + $backuppassword_unsensitive = if $backuppassword =~ Sensitive { + $backuppassword.unwrap + } else { + $backuppassword + } + unless $::osfamily == 'FreeBSD' { if $backupcompress and $compression_command == 'bzcat -zc' { ensure_packages(['bzip2']) Package['bzip2'] -> File['mysqlbackup.sh'] } } mysql_user { "${backupuser}@localhost": ensure => $ensure, password_hash => mysql::password($backuppassword), require => Class['mysql::server::root_password'], } if $include_triggers { $privs = ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER'] } else { $privs = ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS'] } mysql_grant { "${backupuser}@localhost/*.*": ensure => $ensure, user => "${backupuser}@localhost", table => '*.*', privileges => $privs, require => Mysql_user["${backupuser}@localhost"], } if $install_cron { if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '5' { ensure_packages('crontabs') } elsif $::osfamily == 'RedHat' { ensure_packages('cronie') } elsif $::osfamily != 'FreeBSD' { ensure_packages('cron') } } cron { 'mysql-backup': ensure => $ensure, command => '/usr/local/sbin/mysqlbackup.sh', user => 'root', hour => $time[0], minute => $time[1], monthday => $time[2], month => $time[3], weekday => $time[4], require => File['mysqlbackup.sh'], } + # TODO: use EPP instead of ERB, as EPP can handle Data of Type Sensitive without further ado file { 'mysqlbackup.sh': ensure => $ensure, path => '/usr/local/sbin/mysqlbackup.sh', mode => '0700', owner => 'root', group => $mysql::params::root_group, content => template('mysql/mysqlbackup.sh.erb'), } if $mysqlbackupdir_target { file { $backupdir: ensure => $mysqlbackupdir_ensure, target => $mysqlbackupdir_target, mode => $backupdirmode, owner => $backupdirowner, group => $backupdirgroup, } } else { file { $backupdir: ensure => $mysqlbackupdir_ensure, mode => $backupdirmode, owner => $backupdirowner, group => $backupdirgroup, } } } diff --git a/manifests/backup/xtrabackup.pp b/manifests/backup/xtrabackup.pp index 44daae2..1bfd1e7 100644 --- a/manifests/backup/xtrabackup.pp +++ b/manifests/backup/xtrabackup.pp @@ -1,132 +1,139 @@ # @summary # "Provider" for Percona XtraBackup/MariaBackup # @api private # class mysql::backup::xtrabackup ( $xtrabackup_package_name = $mysql::params::xtrabackup_package_name, $backupuser = undef, - $backuppassword = undef, + Optional[Variant[String, Sensitive[String]]] $backuppassword = undef, $backupdir = '', $maxallowedpacket = '1M', $backupmethod = 'xtrabackup', $backupdirmode = '0700', $backupdirowner = 'root', $backupdirgroup = $mysql::params::root_group, $backupcompress = true, $backuprotate = 30, $backupscript_template = 'mysql/xtrabackup.sh.erb', $backup_success_file_path = undef, $ignore_events = true, $delete_before_dump = false, $backupdatabases = [], $file_per_database = false, $include_triggers = true, $include_routines = false, $ensure = 'present', $time = ['23', '5'], $prescript = false, $postscript = false, $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', $optional_args = [], $additional_cron_args = '--backup', $incremental_backups = true, $install_cron = true, $compression_command = undef, $compression_extension = undef, ) inherits mysql::params { ensure_packages($xtrabackup_package_name) + $backuppassword_unsensitive = if $backuppassword =~ Sensitive { + $backuppassword.unwrap + } else { + $backuppassword + } + if $backupuser and $backuppassword { mysql_user { "${backupuser}@localhost": ensure => $ensure, password_hash => mysql::password($backuppassword), require => Class['mysql::server::root_password'], } mysql_grant { "${backupuser}@localhost/*.*": ensure => $ensure, user => "${backupuser}@localhost", table => '*.*', privileges => ['RELOAD', 'PROCESS', 'LOCK TABLES', 'REPLICATION CLIENT'], require => Mysql_user["${backupuser}@localhost"], } } if $install_cron { if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '5' { ensure_packages('crontabs') } elsif $::osfamily == 'RedHat' { ensure_packages('cronie') } elsif $::osfamily != 'FreeBSD' { ensure_packages('cron') } } if $incremental_backups { # Warn if old backups are removed too soon. Incremental backups will fail # if the full backup is no longer available. if ($backuprotate.convert_to(Integer) < 7) { warning('The value for `backuprotate` is too low, it must be set to at least 7 days when using incremental backups.') } # The --target-dir uses a more predictable value for the full backup so # that it can easily be calculated and used in incremental backup jobs. # Besides that it allows to have multiple full backups. cron { 'xtrabackup-weekly': ensure => $ensure, command => "/usr/local/sbin/xtrabackup.sh --target-dir=${backupdir}/$(date +\\%F)_full ${additional_cron_args}", user => 'root', hour => $time[0], minute => $time[1], weekday => '0', require => Package[$xtrabackup_package_name], } } # Wether to use GNU or BSD date format. case $::osfamily { 'FreeBSD','OpenBSD': { $dateformat = '$(date -v-sun +\\%F)_full' } default: { $dateformat = '$(date -d "last sunday" +\\%F)_full' } } $daily_cron_data = ($incremental_backups) ? { true => { 'directories' => "--incremental-basedir=${backupdir}/${dateformat} --target-dir=${backupdir}/$(date +\\%F_\\%H-\\%M-\\%S)", 'weekday' => '1-6', }, false => { 'directories' => "--target-dir=${backupdir}/$(date +\\%F_\\%H-\\%M-\\%S)", 'weekday' => '*', }, } cron { 'xtrabackup-daily': ensure => $ensure, command => "/usr/local/sbin/xtrabackup.sh ${daily_cron_data['directories']} ${additional_cron_args}", user => 'root', hour => $time[0], minute => $time[1], weekday => $daily_cron_data['weekday'], require => Package[$xtrabackup_package_name], } file { $backupdir: ensure => 'directory', mode => $backupdirmode, owner => $backupdirowner, group => $backupdirgroup, } + # TODO: use EPP instead of ERB, as EPP can handle Data of Type Sensitive without further ado file { 'xtrabackup.sh': ensure => $ensure, path => '/usr/local/sbin/xtrabackup.sh', mode => '0700', owner => 'root', group => $mysql::params::root_group, content => template($backupscript_template), } } diff --git a/manifests/bindings.pp b/manifests/bindings.pp index 401d8fe..6e29f62 100644 --- a/manifests/bindings.pp +++ b/manifests/bindings.pp @@ -1,126 +1,126 @@ # @summary # Parent class for MySQL bindings. # # @example Install Ruby language bindings # class { 'mysql::bindings': # ruby_enable => true, # ruby_package_ensure => 'present', # ruby_package_name => 'ruby-mysql-2.7.1-1mdv2007.0.sparc.rpm', # ruby_package_provider => 'rpm', # } # @param install_options # Passes `install_options` array to managed package resources. You must pass the [appropriate options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) for the package manager(s). # @param java_enable # Specifies whether `::mysql::bindings::java` should be included. Valid values are `true`, `false`. # @param perl_enable # Specifies whether `mysql::bindings::perl` should be included. Valid values are `true`, `false`. # @param php_enable # Specifies whether `mysql::bindings::php` should be included. Valid values are `true`, `false`. # @param python_enable # Specifies whether `mysql::bindings::python` should be included. Valid values are `true`, `false`. # @param ruby_enable # Specifies whether `mysql::bindings::ruby` should be included. Valid values are `true`, `false`. # @param client_dev # Specifies whether `::mysql::bindings::client_dev` should be included. Valid values are `true`', `false`. # @param daemon_dev # Specifies whether `::mysql::bindings::daemon_dev` should be included. Valid values are `true`, `false`. # @param java_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `java_enable => true`. # @param java_package_name # The name of the Java package to install. Only applies if `java_enable => true`. # @param java_package_provider # The provider to use to install the Java package. Only applies if `java_enable => true`. # @param perl_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `perl_enable => true`. # @param perl_package_name # The name of the Perl package to install. Only applies if `perl_enable => true`. # @param perl_package_provider # The provider to use to install the Perl package. Only applies if `perl_enable => true`. # @param php_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `php_enable => true`. # @param php_package_name # The name of the PHP package to install. Only applies if `php_enable => true`. # @param php_package_provider # The provider to use to install the PHP package. Only applies if `php_enable => true`. # @param python_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `python_enable => true`. # @param python_package_name # The name of the Python package to install. Only applies if `python_enable => true`. # @param python_package_provider # The provider to use to install the Python package. Only applies if `python_enable => true`. # @param ruby_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `ruby_enable => true`. # @param ruby_package_name # The name of the Ruby package to install. Only applies if `ruby_enable => true`. # @param ruby_package_provider # What provider should be used to install the package. # @param client_dev_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `client_dev => true`. # @param client_dev_package_name # The name of the client_dev package to install. Only applies if `client_dev => true`. # @param client_dev_package_provider # The provider to use to install the client_dev package. Only applies if `client_dev => true`. # @param daemon_dev_package_ensure # Whether the package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Only applies if `daemon_dev => true`. # @param daemon_dev_package_name # The name of the daemon_dev package to install. Only applies if `daemon_dev => true`. # @param daemon_dev_package_provider # The provider to use to install the daemon_dev package. Only applies if `daemon_dev => true`. # class mysql::bindings ( $install_options = undef, # Boolean to determine if we should include the classes. $java_enable = false, $perl_enable = false, $php_enable = false, $python_enable = false, $ruby_enable = false, $client_dev = false, $daemon_dev = false, # Settings for the various classes. $java_package_ensure = $mysql::params::java_package_ensure, $java_package_name = $mysql::params::java_package_name, $java_package_provider = $mysql::params::java_package_provider, $perl_package_ensure = $mysql::params::perl_package_ensure, $perl_package_name = $mysql::params::perl_package_name, $perl_package_provider = $mysql::params::perl_package_provider, $php_package_ensure = $mysql::params::php_package_ensure, $php_package_name = $mysql::params::php_package_name, $php_package_provider = $mysql::params::php_package_provider, $python_package_ensure = $mysql::params::python_package_ensure, $python_package_name = $mysql::params::python_package_name, $python_package_provider = $mysql::params::python_package_provider, $ruby_package_ensure = $mysql::params::ruby_package_ensure, $ruby_package_name = $mysql::params::ruby_package_name, $ruby_package_provider = $mysql::params::ruby_package_provider, $client_dev_package_ensure = $mysql::params::client_dev_package_ensure, $client_dev_package_name = $mysql::params::client_dev_package_name, $client_dev_package_provider = $mysql::params::client_dev_package_provider, $daemon_dev_package_ensure = $mysql::params::daemon_dev_package_ensure, $daemon_dev_package_name = $mysql::params::daemon_dev_package_name, $daemon_dev_package_provider = $mysql::params::daemon_dev_package_provider ) inherits mysql::params { case $::osfamily { 'Archlinux': { - if $java_enable { fail("::mysql::bindings::java cannot be managed by puppet on ${osfamily} + if $java_enable { fail("::mysql::bindings::java cannot be managed by puppet on ${::facts['os']['family']} as it is not in official repositories. Please disable java mysql binding.") } if $perl_enable { include 'mysql::bindings::perl' } - if $php_enable { warning("::mysql::bindings::php does not need to be managed by puppet on ${osfamily} + if $php_enable { warning("::mysql::bindings::php does not need to be managed by puppet on ${::facts['os']['family']} as it is included in mysql package by default.") } if $python_enable { include 'mysql::bindings::python' } - if $ruby_enable { fail("::mysql::bindings::ruby cannot be managed by puppet on %{osfamily} + if $ruby_enable { fail("::mysql::bindings::ruby cannot be managed by puppet on %{::facts['os']['family']} as it is not in official repositories. Please disable ruby mysql binding.") } } default: { if $java_enable { include 'mysql::bindings::java' } if $perl_enable { include 'mysql::bindings::perl' } if $php_enable { include 'mysql::bindings::php' } if $python_enable { include 'mysql::bindings::python' } if $ruby_enable { include 'mysql::bindings::ruby' } } } if $client_dev { include 'mysql::bindings::client_dev' } if $daemon_dev { include 'mysql::bindings::daemon_dev' } } diff --git a/manifests/bindings/client_dev.pp b/manifests/bindings/client_dev.pp index e9e762b..407509d 100644 --- a/manifests/bindings/client_dev.pp +++ b/manifests/bindings/client_dev.pp @@ -1,17 +1,17 @@ # @summary # Private class for installing client development bindings # # @api private # class mysql::bindings::client_dev { if $mysql::bindings::client_dev_package_name { package { 'mysql-client_dev': ensure => $mysql::bindings::client_dev_package_ensure, install_options => $mysql::bindings::install_options, name => $mysql::bindings::client_dev_package_name, provider => $mysql::bindings::client_dev_package_provider, } } else { - warning("No MySQL client development package configured for ${os}.") + warning("No MySQL client development package configured for ${::facts['os']['family']}.") } } diff --git a/manifests/bindings/daemon_dev.pp b/manifests/bindings/daemon_dev.pp index 8f4e11f..c780f0e 100644 --- a/manifests/bindings/daemon_dev.pp +++ b/manifests/bindings/daemon_dev.pp @@ -1,17 +1,17 @@ # @summary # Private class for installing daemon development bindings # # @api private # class mysql::bindings::daemon_dev { if $mysql::bindings::daemon_dev_package_name { package { 'mysql-daemon_dev': ensure => $mysql::bindings::daemon_dev_package_ensure, install_options => $mysql::bindings::install_options, name => $mysql::bindings::daemon_dev_package_name, provider => $mysql::bindings::daemon_dev_package_provider, } } else { - warning("No MySQL daemon development package configured for ${os}.") + warning("No MySQL daemon development package configured for ${::facts['os']['family']}.") } } diff --git a/manifests/db.pp b/manifests/db.pp index 1b8688d..52cc382 100644 --- a/manifests/db.pp +++ b/manifests/db.pp @@ -1,114 +1,114 @@ # @summary # Create and configure a MySQL database. # # @example Create a database # mysql::db { 'mydb': # user => 'myuser', # password => 'mypass', # host => 'localhost', # grant => ['SELECT', 'UPDATE'], # } # # @param user # The user for the database you're creating. # @param password # The password for $user for the database you're creating. # @param tls_options # The tls_options for $user for the database you're creating. # @param dbname # The name of the database to create. # @param charset # The character set for the database. # @param collate # The collation for the database. # @param host # The host to use as part of user@host for grants. # @param grant # The privileges to be granted for user@host on the database. # @param grant_options # The grant_options for the grant for user@host on the database. # @param sql # The path to the sqlfile you want to execute. This can be single file specified as string, or it can be an array of strings. # @param enforce_sql # Specifies whether executing the sqlfiles should happen on every run. If set to false, sqlfiles only run once. # @param ensure # Specifies whether to create the database. Valid values are 'present', 'absent'. Defaults to 'present'. # @param import_timeout # Timeout, in seconds, for loading the sqlfiles. Defaults to 300. # @param import_cat_cmd # Command to read the sqlfile for importing the database. Useful for compressed sqlfiles. For example, you can use 'zcat' for .gz files. # define mysql::db ( $user, - $password, + Variant[String, Sensitive[String]] $password, $tls_options = undef, $dbname = $name, $charset = 'utf8', $collate = 'utf8_general_ci', $host = 'localhost', $grant = 'ALL', $grant_options = undef, Optional[Variant[Array, Hash, String]] $sql = undef, $enforce_sql = false, Enum['absent', 'present'] $ensure = 'present', $import_timeout = 300, $import_cat_cmd = 'cat', $mysql_exec_path = undef, ) { $table = "${dbname}.*" $sql_inputs = join([$sql], ' ') include 'mysql::client' if ($mysql_exec_path) { $_mysql_exec_path = $mysql_exec_path } else { $_mysql_exec_path = $mysql::params::exec_path } $db_resource = { ensure => $ensure, charset => $charset, collate => $collate, provider => 'mysql', require => [Class['mysql::client']], } ensure_resource('mysql_database', $dbname, $db_resource) $user_resource = { ensure => $ensure, password_hash => mysql::password($password), tls_options => $tls_options, } ensure_resource('mysql_user', "${user}@${host}", $user_resource) if $ensure == 'present' { mysql_grant { "${user}@${host}/${table}": privileges => $grant, provider => 'mysql', user => "${user}@${host}", table => $table, options => $grant_options, require => [ Mysql_database[$dbname], Mysql_user["${user}@${host}"], ], } $refresh = ! $enforce_sql if $sql { exec { "${dbname}-import": command => "${import_cat_cmd} ${sql_inputs} | mysql ${dbname}", logoutput => true, environment => "HOME=${::root_home}", refreshonly => $refresh, path => "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:${_mysql_exec_path}", require => Mysql_grant["${user}@${host}/${table}"], subscribe => Mysql_database[$dbname], timeout => $import_timeout, } } } } diff --git a/manifests/params.pp b/manifests/params.pp index 8104430..e9459aa 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -1,584 +1,584 @@ # @summary # Params class. # # @api private # class mysql::params { $manage_config_file = true $config_file_mode = '0644' $purge_conf_dir = false $restart = false $root_password = 'UNSET' $install_secret_file = '/.mysql_secret' $server_package_ensure = 'present' $server_package_manage = true $server_service_manage = true $server_service_enabled = true $client_package_ensure = 'present' $client_package_manage = true $create_root_user = true $create_root_my_cnf = true $create_root_login_file = false $login_file = undef $exec_path = '' # mysql::bindings $bindings_enable = false $java_package_ensure = 'present' $java_package_provider = undef $perl_package_ensure = 'present' $perl_package_provider = undef $php_package_ensure = 'present' $php_package_provider = undef $python_package_ensure = 'present' $python_package_provider = undef $ruby_package_ensure = 'present' $ruby_package_provider = undef $client_dev_package_ensure = 'present' $client_dev_package_provider = undef $daemon_dev_package_ensure = 'present' $daemon_dev_package_provider = undef $xtrabackup_package_name_default = 'percona-xtrabackup' case $::osfamily { 'RedHat': { case $::operatingsystem { 'Fedora': { if versioncmp($::operatingsystemrelease, '19') >= 0 or $::operatingsystemrelease == 'Rawhide' { $provider = 'mariadb' } else { $provider = 'mysql' } $python_package_name = 'MySQL-python' } 'Amazon': { if versioncmp($::operatingsystemrelease, '2') >= 0 { $provider = 'mariadb' } else { $provider = 'mysql' } } /^(RedHat|CentOS|Scientific|OracleLinux)$/: { if versioncmp($::operatingsystemmajrelease, '7') >= 0 { $provider = 'mariadb' if versioncmp($::operatingsystemmajrelease, '8') >= 0 { $xtrabackup_package_name_override = 'percona-xtrabackup-24' } } else { $provider = 'mysql' $xtrabackup_package_name_override = 'percona-xtrabackup-20' } if versioncmp($::operatingsystemmajrelease, '8') >= 0 { $java_package_name = 'mariadb-java-client' $python_package_name = 'python3-PyMySQL' } else { $java_package_name = 'mysql-connector-java' $python_package_name = 'MySQL-python' } } default: { $provider = 'mysql' } } if $provider == 'mariadb' { $client_package_name = 'mariadb' $server_package_name = 'mariadb-server' $server_service_name = 'mariadb' $log_error = '/var/log/mariadb/mariadb.log' $config_file = '/etc/my.cnf.d/server.cnf' # mariadb package by default has !includedir set in my.cnf to /etc/my.cnf.d $includedir = undef $pidfile = '/var/run/mariadb/mariadb.pid' $daemon_dev_package_name = 'mariadb-devel' } else { $client_package_name = 'mysql' $server_package_name = 'mysql-server' $server_service_name = 'mysqld' $log_error = '/var/log/mysqld.log' $config_file = '/etc/my.cnf' $includedir = '/etc/my.cnf.d' $pidfile = '/var/run/mysqld/mysqld.pid' $daemon_dev_package_name = 'mysql-devel' } $basedir = '/usr' $datadir = '/var/lib/mysql' $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $socket = '/var/lib/mysql/mysql.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $perl_package_name = 'perl-DBD-MySQL' $php_package_name = 'php-mysql' $ruby_package_name = 'ruby-mysql' $client_dev_package_name = undef } 'Suse': { case $::operatingsystem { 'OpenSuSE': { if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { $client_package_name = 'mariadb-client' $server_package_name = 'mariadb' # First service start fails if this is set. Runs fine without # it being set, in any case. Leaving it as-is for the mysql. $basedir = undef } else { $client_package_name = 'mysql-community-server-client' $server_package_name = 'mysql-community-server' $basedir = '/usr' } } 'SLES','SLED': { if versioncmp($::operatingsystemrelease, '12') >= 0 { $client_package_name = 'mariadb-client' $server_package_name = 'mariadb' $basedir = undef } else { $client_package_name = 'mysql-client' $server_package_name = 'mysql' $basedir = '/usr' } } default: { fail("Unsupported platform: puppetlabs-${module_name} currently doesn\'t support ${::operatingsystem}.") } } $config_file = '/etc/my.cnf' $includedir = '/etc/my.cnf.d' $datadir = '/var/lib/mysql' $log_error = $::operatingsystem ? { /OpenSuSE/ => '/var/log/mysql/mysqld.log', /(SLES|SLED)/ => '/var/log/mysqld.log', } $pidfile = $::operatingsystem ? { /OpenSuSE/ => '/var/run/mysql/mysqld.pid', /(SLES|SLED)/ => '/var/lib/mysql/mysqld.pid', } $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysql' $xtrabackup_package_name_override = 'xtrabackup' if $::operatingsystem =~ /(SLES|SLED)/ { if versioncmp( $::operatingsystemmajrelease, '12' ) >= 0 { $socket = '/run/mysql/mysql.sock' } else { $socket = '/var/lib/mysql/mysql.sock' } } else { $socket = '/var/run/mysql/mysql.sock' } $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = 'mysql-connector-java' $perl_package_name = 'perl-DBD-mysql' $php_package_name = 'apache2-mod_php53' $python_package_name = 'python-mysql' $ruby_package_name = $::operatingsystem ? { /OpenSuSE/ => 'rubygem-mysql', /(SLES|SLED)/ => 'ruby-mysql', } $client_dev_package_name = 'libmysqlclient-devel' $daemon_dev_package_name = 'mysql-devel' } 'Debian': { if $::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '9') >= 0 { $provider = 'mariadb' } else { $provider = 'mysql' } if $provider == 'mariadb' { $client_package_name = 'mariadb-client' $server_package_name = 'mariadb-server' $server_service_name = 'mariadb' $client_dev_package_name = 'libmariadbclient-dev' $daemon_dev_package_name = 'libmariadbd-dev' } else { $client_package_name = 'mysql-client' $server_package_name = 'mysql-server' $server_service_name = 'mysql' $client_dev_package_name = 'libmysqlclient-dev' $daemon_dev_package_name = 'libmysqld-dev' } $basedir = '/usr' $config_file = '/etc/mysql/my.cnf' $includedir = '/etc/mysql/conf.d' $datadir = '/var/lib/mysql' $log_error = '/var/log/mysql/error.log' $pidfile = '/var/run/mysqld/mysqld.pid' $root_group = 'root' $mysql_group = 'adm' $mycnf_owner = undef $mycnf_group = undef $socket = '/var/run/mysqld/mysqld.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = ['tmpdir','basedir','datadir','innodb_data_home_dir','innodb_log_group_home_dir','innodb_undo_directory','innodb_tmpdir'] # mysql::bindings if ($::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '10') >= 0) or ($::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemrelease, '20.04') >= 0) { $java_package_name = 'libmariadb-java' } else { $java_package_name = 'libmysql-java' } $perl_package_name = 'libdbd-mysql-perl' if ($::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemrelease, '16.04') >= 0) or ($::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '9') >= 0) { $php_package_name = 'php-mysql' } else { $php_package_name = 'php5-mysql' } if ($::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemrelease, '16.04') < 0) or ($::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemrelease, '20.04') >= 0) or ($::operatingsystem == 'Debian') { $xtrabackup_package_name_override = 'percona-xtrabackup-24' } if ($::operatingsystem == 'Ubuntu' and versioncmp($::operatingsystemrelease, '20.04') >= 0) or ($::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '11') >= 0){ $python_package_name = 'python3-mysqldb' } else { $python_package_name = 'python-mysqldb' } - $ruby_package_name = $facts['operatingsystemmajrelease'] ? { + $ruby_package_name = $facts['os']['release']['major'] ? { '8' => 'ruby-mysql', # jessie '9' => 'ruby-mysql2', # stretch '10' => 'ruby-mysql2', # buster '14.04' => 'ruby-mysql', # trusty '16.04' => 'ruby-mysql', # xenial '18.04' => 'ruby-mysql2', # bionic '20.04' => 'ruby-mysql2', # focal default => 'libmysql-ruby', } } 'Archlinux': { $daemon_dev_package_name = undef $client_dev_package_name = undef $includedir = undef $client_package_name = 'mariadb-clients' $server_package_name = 'mariadb' $basedir = '/usr' $config_file = '/etc/mysql/my.cnf' $datadir = '/var/lib/mysql' $log_error = '/var/log/mysqld.log' $pidfile = '/var/run/mysqld/mysqld.pid' $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysqld' $socket = '/var/lib/mysql/mysql.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = 'mysql-connector-java' $perl_package_name = 'perl-dbd-mysql' $php_package_name = undef $python_package_name = 'mysql-python' $ruby_package_name = 'mysql-ruby' } 'Gentoo': { $client_package_name = 'virtual/mysql' $includedir = undef $server_package_name = 'virtual/mysql' $basedir = '/usr' $config_file = '/etc/mysql/my.cnf' $datadir = '/var/lib/mysql' $log_error = '/var/log/mysql/mysqld.err' $pidfile = '/run/mysqld/mysqld.pid' $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysql' $socket = '/run/mysqld/mysqld.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = 'dev-java/jdbc-mysql' $perl_package_name = 'dev-perl/DBD-mysql' $php_package_name = undef $python_package_name = 'dev-python/mysql-python' $ruby_package_name = 'dev-ruby/mysql-ruby' } 'FreeBSD': { $client_package_name = 'databases/mysql57-client' $server_package_name = 'databases/mysql57-server' $basedir = '/usr/local' $config_file = '/usr/local/etc/my.cnf' $includedir = '/usr/local/etc/my.cnf.d' $datadir = '/var/db/mysql' $log_error = '/var/log/mysqld.log' $pidfile = '/var/run/mysql.pid' $root_group = 'wheel' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysql-server' $socket = '/var/db/mysql/mysql.sock' $ssl_ca = undef $ssl_cert = undef $ssl_key = undef $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = 'databases/mysql-connector-java' $perl_package_name = 'p5-DBD-mysql' $php_package_name = 'php5-mysql' $python_package_name = 'databases/py-MySQLdb' $ruby_package_name = 'databases/ruby-mysql' # The libraries installed by these packages are included in client and server packages, no installation required. $client_dev_package_name = undef $daemon_dev_package_name = undef } 'OpenBSD': { $client_package_name = 'mariadb-client' $server_package_name = 'mariadb-server' $basedir = '/usr/local' $config_file = '/etc/my.cnf' $includedir = undef $datadir = '/var/mysql' $log_error = "/var/mysql/${::hostname}.err" $pidfile = '/var/mysql/mysql.pid' $root_group = 'wheel' $mysql_group = '_mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysqld' $socket = '/var/run/mysql/mysql.sock' $ssl_ca = undef $ssl_cert = undef $ssl_key = undef $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = undef $perl_package_name = 'p5-DBD-mysql' $php_package_name = 'php-mysql' $python_package_name = 'py-mysql' $ruby_package_name = 'ruby-mysql' # The libraries installed by these packages are included in client and server packages, no installation required. $client_dev_package_name = undef $daemon_dev_package_name = undef } 'Solaris': { $client_package_name = 'database/mysql-55/client' $server_package_name = 'database/mysql-55' $basedir = undef $config_file = '/etc/mysql/5.5/my.cnf' $datadir = '/var/mysql/5.5/data' $log_error = "/var/mysql/5.5/data/${::hostname}.err" $pidfile = "/var/mysql/5.5/data/${::hostname}.pid" $root_group = 'bin' $server_service_name = 'application/database/mysql:version_55' $socket = '/tmp/mysql.sock' $ssl_ca = undef $ssl_cert = undef $ssl_key = undef $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = undef $perl_package_name = undef $php_package_name = 'web/php-53/extension/php-mysql' $python_package_name = 'library/python/python-mysql' $ruby_package_name = undef # The libraries installed by these packages are included in client and server packages, no installation required. $client_dev_package_name = undef $daemon_dev_package_name = undef } default: { case $::operatingsystem { 'Alpine': { $client_package_name = 'mariadb-client' $server_package_name = 'mariadb' $basedir = '/usr' $config_file = '/etc/mysql/my.cnf' $datadir = '/var/lib/mysql' $log_error = '/var/log/mysqld.log' $pidfile = '/run/mysqld/mysqld.pid' $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mariadb' $socket = '/run/mysqld/mysqld.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef $java_package_name = undef $perl_package_name = 'perl-dbd-mysql' $php_package_name = 'php7-mysqlnd' $python_package_name = 'py-mysqldb' $ruby_package_name = undef $client_dev_package_name = undef $daemon_dev_package_name = undef } 'Amazon': { $client_package_name = 'mysql' $server_package_name = 'mysql-server' $basedir = '/usr' $config_file = '/etc/my.cnf' $includedir = '/etc/my.cnf.d' $datadir = '/var/lib/mysql' $log_error = '/var/log/mysqld.log' $pidfile = '/var/run/mysqld/mysqld.pid' $root_group = 'root' $mysql_group = 'mysql' $mycnf_owner = undef $mycnf_group = undef $server_service_name = 'mysqld' $socket = '/var/lib/mysql/mysql.sock' $ssl_ca = '/etc/mysql/cacert.pem' $ssl_cert = '/etc/mysql/server-cert.pem' $ssl_key = '/etc/mysql/server-key.pem' $tmpdir = '/tmp' $managed_dirs = undef # mysql::bindings $java_package_name = 'mysql-connector-java' $perl_package_name = 'perl-DBD-MySQL' $php_package_name = 'php-mysql' $python_package_name = 'MySQL-python' $ruby_package_name = 'ruby-mysql' # The libraries installed by these packages are included in client and server packages, no installation required. $client_dev_package_name = undef $daemon_dev_package_name = undef } default: { fail("Unsupported platform: puppetlabs-${module_name} currently doesn\'t support ${::osfamily} or ${::operatingsystem}.") } } } } case $::operatingsystem { 'Ubuntu': { # lint:ignore:only_variable_string if versioncmp("${::operatingsystemmajrelease}", '14.10') > 0 { # lint:endignore $server_service_provider = 'systemd' } else { $server_service_provider = 'upstart' } } 'Alpine': { $server_service_provider = 'rc-service' } 'FreeBSD': { $server_service_provider = 'freebsd' } default: { $server_service_provider = undef } } $default_options = { 'client' => { 'port' => '3306', 'socket' => $mysql::params::socket, }, 'mysqld_safe' => { 'nice' => '0', 'log-error' => $mysql::params::log_error, 'socket' => $mysql::params::socket, }, 'mysqld-5.0' => { 'myisam-recover' => 'BACKUP', }, 'mysqld-5.1' => { 'myisam-recover' => 'BACKUP', }, 'mysqld-5.5' => { 'myisam-recover' => 'BACKUP', 'query_cache_limit' => '1M', 'query_cache_size' => '16M', }, 'mysqld-5.6' => { 'myisam-recover-options' => 'BACKUP', 'query_cache_limit' => '1M', 'query_cache_size' => '16M', }, 'mysqld-5.7' => { 'myisam-recover-options' => 'BACKUP', 'query_cache_limit' => '1M', 'query_cache_size' => '16M', }, 'mysqld' => { 'basedir' => $mysql::params::basedir, 'bind-address' => '127.0.0.1', 'datadir' => $mysql::params::datadir, 'expire_logs_days' => '10', 'key_buffer_size' => '16M', 'log-error' => $mysql::params::log_error, 'max_allowed_packet' => '16M', 'max_binlog_size' => '100M', 'max_connections' => '151', 'pid-file' => $mysql::params::pidfile, 'port' => '3306', 'skip-external-locking' => true, 'socket' => $mysql::params::socket, 'ssl' => false, 'ssl-ca' => $mysql::params::ssl_ca, 'ssl-cert' => $mysql::params::ssl_cert, 'ssl-key' => $mysql::params::ssl_key, 'ssl-disable' => false, 'thread_cache_size' => '8', 'thread_stack' => '256K', 'tmpdir' => $mysql::params::tmpdir, 'user' => 'mysql', }, 'mysqldump' => { 'max_allowed_packet' => '16M', 'quick' => true, 'quote-names' => true, }, 'isamchk' => { 'key_buffer_size' => '16M', }, } if defined('$xtrabackup_package_name_override') { $xtrabackup_package_name = pick($xtrabackup_package_name_override, $xtrabackup_package_name_default) } else { $xtrabackup_package_name = $xtrabackup_package_name_default } ## Additional graceful failures if $::osfamily == 'RedHat' and $::operatingsystemmajrelease == '4' and $::operatingsystem != 'Amazon' { fail("Unsupported platform: puppetlabs-${module_name} only supports RedHat 5.0 and beyond.") } } diff --git a/manifests/server.pp b/manifests/server.pp index 461cb6f..4ad75ac 100644 --- a/manifests/server.pp +++ b/manifests/server.pp @@ -1,178 +1,178 @@ # @summary # Installs and configures the MySQL server. # # @example Install MySQL Server # class { '::mysql::server': # package_name => 'mysql-server', # package_ensure => '5.7.1+mysql~trusty', # root_password => 'strongpassword', # remove_default_accounts => true, # } # # @param config_file # The location, as a path, of the MySQL configuration file. # @param config_file_mode # The MySQL configuration file's permissions mode. # @param includedir # The location, as a path, of !includedir for custom configuration overrides. # @param install_options # Passes [install_options](https://docs.puppetlabs.com/references/latest/type.html#package-attribute-install_options) array to managed package resources. You must pass the appropriate options for the specified package manager # @param install_secret_file # Path to secret file containing temporary root password. # @param manage_config_file # Whether the MySQL configuration file should be managed. Valid values are `true`, `false`. Defaults to `true`. # @param options # A hash of options structured like the override_options, but not merged with the default options. Use this if you don't want your options merged with the default options. # @param override_options # Specifies override options to pass into MySQL. Structured like a hash in the my.cnf file: See above for usage details. # @param package_ensure # Whether the package exists or should be a specific version. Valid values are 'present', 'absent', or 'x.y.z'. Defaults to 'present'. # @param package_manage # Whether to manage the MySQL server package. Defaults to `true`. # @param package_name # The name of the MySQL server package to install. # @param package_provider # Define a specific provider for package install. # @param package_source # The location of the package source (require for some package provider) # @param purge_conf_dir # Whether the `includedir` directory should be purged. Valid values are `true`, `false`. Defaults to `false`. # @param remove_default_accounts # Specifies whether to automatically include `mysql::server::account_security`. Valid values are `true`, `false`. Defaults to `false`. # @param restart # Whether the service should be restarted when things change. Valid values are `true`, `false`. Defaults to `false`. # @param root_group # The name of the group used for root. Can be a group name or a group ID. See more about the [group](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). # @param mysql_group # The name of the group of the MySQL daemon user. Can be a group name or a group ID. See more about the [group](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-group). # @param mycnf_owner # Name or user-id who owns the mysql-config-file. # @param mycnf_group # Name or group-id which owns the mysql-config-file. # @param root_password # The MySQL root password. Puppet attempts to set the root password and update `/root/.my.cnf` with it. This is required if `create_root_user` or `create_root_my_cnf` are true. If `root_password` is 'UNSET', then `create_root_user` and `create_root_my_cnf` are assumed to be false --- that is, the MySQL root user and `/root/.my.cnf` are not created. Password changes are supported; however, the old password must be set in `/root/.my.cnf`. Effectively, Puppet uses the old password, configured in `/root/my.cnf`, to set the new password in MySQL, and then updates `/root/.my.cnf` with the new password. # @param service_enabled # Specifies whether the service should be enabled. Valid values are `true`, `false`. Defaults to `true`. # @param service_manage # Specifies whether the service should be managed. Valid values are `true`, `false`. Defaults to `true`. # @param service_name # The name of the MySQL server service. Defaults are OS dependent, defined in 'params.pp'. # @param service_provider # The provider to use to manage the service. For Ubuntu, defaults to 'upstart'; otherwise, default is undefined. # @param create_root_user # Whether root user should be created. Valid values are `true`, `false`. Defaults to `true`. This is useful for a cluster setup with Galera. The root user has to be created only once. You can set this parameter true on one node and set it to false on the remaining nodes. # @param create_root_my_cnf # Whether to create `/root/.my.cnf`. Valid values are `true`, `false`. Defaults to `true`. `create_root_my_cnf` allows creation of `/root/.my.cnf` independently of `create_root_user`. You can use this for a cluster setup with Galera where you want `/root/.my.cnf` to exist on all nodes. # @param users # Optional hash of users to create, which are passed to [mysql_user](#mysql_user). # @param grants # Optional hash of grants, which are passed to [mysql_grant](#mysql_grant). # @param databases # Optional hash of databases to create, which are passed to [mysql_database](#mysql_database). # @param enabled # _Deprecated_ # @param manage_service # _Deprecated_ # @param old_root_password # This parameter no longer does anything. It exists only for backwards compatibility. See the `root_password` parameter above for details on changing the root password. # class mysql::server ( $config_file = $mysql::params::config_file, $config_file_mode = $mysql::params::config_file_mode, $includedir = $mysql::params::includedir, $install_options = undef, $install_secret_file = $mysql::params::install_secret_file, $manage_config_file = $mysql::params::manage_config_file, Mysql::Options $options = {}, $override_options = {}, $package_ensure = $mysql::params::server_package_ensure, $package_manage = $mysql::params::server_package_manage, $package_name = $mysql::params::server_package_name, $package_provider = undef, $package_source = undef, $purge_conf_dir = $mysql::params::purge_conf_dir, $remove_default_accounts = false, $restart = $mysql::params::restart, $root_group = $mysql::params::root_group, $managed_dirs = $mysql::params::managed_dirs, $mysql_group = $mysql::params::mysql_group, $mycnf_owner = $mysql::params::mycnf_owner, $mycnf_group = $mysql::params::mycnf_group, - $root_password = $mysql::params::root_password, + Variant[String, Sensitive[String]] $root_password = $mysql::params::root_password, $service_enabled = $mysql::params::server_service_enabled, $service_manage = $mysql::params::server_service_manage, $service_name = $mysql::params::server_service_name, $service_provider = $mysql::params::server_service_provider, $create_root_user = $mysql::params::create_root_user, $create_root_my_cnf = $mysql::params::create_root_my_cnf, $create_root_login_file = $mysql::params::create_root_login_file, $login_file = $mysql::params::login_file, $users = {}, $grants = {}, $databases = {}, # Deprecated parameters $enabled = undef, $manage_service = undef, $old_root_password = undef ) inherits mysql::params { # Deprecated parameters. if $enabled { crit('This parameter has been renamed to service_enabled.') $real_service_enabled = $enabled } else { $real_service_enabled = $service_enabled } if $manage_service { crit('This parameter has been renamed to service_manage.') $real_service_manage = $manage_service } else { $real_service_manage = $service_manage } if $old_root_password { warning('The `old_root_password` attribute is no longer used and will be removed in a future release.') } if ! empty($options) and ! empty($override_options) { fail('You can\'t specify $options and $override_options simultaneously, see the README section \'Customize server options\'!') } # If override_options are set, create a merged together set of options. Rightmost hashes win over left. # If options are set, just use them. $_options = empty($options) ? { true => mysql::normalise_and_deepmerge($mysql::params::default_options, $override_options), false => $options, } Class['mysql::server::root_password'] -> Mysql::Db <| |> include 'mysql::server::config' include 'mysql::server::install' include 'mysql::server::managed_dirs' include 'mysql::server::installdb' include 'mysql::server::service' include 'mysql::server::root_password' include 'mysql::server::providers' if $remove_default_accounts { class { 'mysql::server::account_security': require => Anchor['mysql::server::end'], } } anchor { 'mysql::server::start': } anchor { 'mysql::server::end': } if $restart { Class['mysql::server::config'] ~> Class['mysql::server::service'] } Anchor['mysql::server::start'] -> Class['mysql::server::config'] -> Class['mysql::server::install'] -> Class['mysql::server::managed_dirs'] -> Class['mysql::server::installdb'] -> Class['mysql::server::service'] -> Class['mysql::server::root_password'] -> Class['mysql::server::providers'] -> Anchor['mysql::server::end'] } diff --git a/manifests/server/backup.pp b/manifests/server/backup.pp index 6fab65c..0233413 100644 --- a/manifests/server/backup.pp +++ b/manifests/server/backup.pp @@ -1,140 +1,140 @@ # @summary # Create and manage a MySQL backup. # # @example Create a basic MySQL backup: # class { 'mysql::server': # root_password => 'password' # } # class { 'mysql::server::backup': # backupuser => 'myuser', # backuppassword => 'mypassword', # backupdir => '/tmp/backups', # } # class { 'mysql::server::backup': # backupmethod => 'mariabackup', # provider => 'xtrabackup', # backupdir => '/tmp/backups', # } # # @param backupuser # MySQL user to create with backup administrator privileges. # @param backuppassword # Password to create for `backupuser`. # @param backupdir # Directory to store backup. # @param backupdirmode # Permissions applied to the backup directory. This parameter is passed directly to the file resource. # @param backupdirowner # Owner for the backup directory. This parameter is passed directly to the file resource. # @param backupdirgroup # Group owner for the backup directory. This parameter is passed directly to the file resource. # @param backupcompress # Whether or not to compress the backup (when using the mysqldump or xtrabackup provider) # @param backupmethod # The execution binary for backing up. ex. mysqldump, xtrabackup, mariabackup # @param backup_success_file_path # Specify a path where upon successfull backup a file should be created for checking purposes. # @param backuprotate # Backup rotation interval in 24 hour periods. # @param ignore_events # Ignore the mysql.event table. # @param delete_before_dump # Whether to delete old .sql files before backing up. Setting to true deletes old files before backing up, while setting to false deletes them after backup. # @param backupdatabases # Databases to backup (required if using xtrabackup provider). By default `[]` will back up all databases. # @param file_per_database # Use file per database mode creating one file per database backup. # @param include_routines # Dump stored routines (procedures and functions) from dumped databases when doing a `file_per_database` backup. # @param include_triggers # Dump triggers for each dumped table when doing a `file_per_database` backup. # @param incremental_backups # A flag to activate/deactivate incremental backups. Currently only supported by the xtrabackup provider. # @param ensure # @param time # An array of two elements to set the backup time. Allows ['23', '5'] (i.e., 23:05) or ['3', '45'] (i.e., 03:45) for HH:MM times. # @param prescript # A script that is executed before the backup begins. # @param postscript # A script that is executed when the backup is finished. This could be used to sync the backup to a central store. This script can be either a single line that is directly executed or a number of lines supplied as an array. It could also be one or more externally managed (executable) files. # @param execpath # Allows you to set a custom PATH should your MySQL installation be non-standard places. Defaults to `/usr/bin:/usr/sbin:/bin:/sbin`. # @param provider # Sets the server backup implementation. Valid values are: # @param maxallowedpacket # Defines the maximum SQL statement size for the backup dump script. The default value is 1MB, as this is the default MySQL Server value. # @param optional_args # Specifies an array of optional arguments which should be passed through to the backup tool. (Supported by the xtrabackup and mysqldump providers.) # @param install_cron # Manage installation of cron package # @param compression_command # Configure the command used to compress the backup (when using the mysqldump provider). Make sure the command exists # on the target system. Packages for it are NOT automatically installed. # @param compression_extension # Configure the file extension for the compressed backup (when using the mysqldump provider) class mysql::server::backup ( $backupuser = undef, - $backuppassword = undef, + Optional[Variant[String, Sensitive[String]]] $backuppassword = undef, $backupdir = undef, $backupdirmode = '0700', $backupdirowner = 'root', $backupdirgroup = $mysql::params::root_group, $backupcompress = true, $backuprotate = 30, $backupmethod = undef, $backup_success_file_path = '/tmp/mysqlbackup_success', $ignore_events = true, $delete_before_dump = false, $backupdatabases = [], $file_per_database = false, $include_routines = false, $include_triggers = false, $ensure = 'present', $time = ['23', '5'], $prescript = false, $postscript = false, $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', $provider = 'mysqldump', $maxallowedpacket = '1M', $optional_args = [], $incremental_backups = true, $install_cron = true, $compression_command = undef, $compression_extension = undef ) inherits mysql::params { if $prescript and $provider =~ /(mysqldump|mysqlbackup)/ { warning("The 'prescript' option is not currently implemented for the ${provider} backup provider.") } create_resources('class', { "mysql::backup::${provider}" => { 'backupuser' => $backupuser, 'backuppassword' => $backuppassword, 'backupdir' => $backupdir, 'backupdirmode' => $backupdirmode, 'backupdirowner' => $backupdirowner, 'backupdirgroup' => $backupdirgroup, 'backupcompress' => $backupcompress, 'backuprotate' => $backuprotate, 'backupmethod' => $backupmethod, 'backup_success_file_path' => $backup_success_file_path, 'ignore_events' => $ignore_events, 'delete_before_dump' => $delete_before_dump, 'backupdatabases' => $backupdatabases, 'file_per_database' => $file_per_database, 'include_routines' => $include_routines, 'include_triggers' => $include_triggers, 'ensure' => $ensure, 'time' => $time, 'prescript' => $prescript, 'postscript' => $postscript, 'execpath' => $execpath, 'maxallowedpacket' => $maxallowedpacket, 'optional_args' => $optional_args, 'incremental_backups' => $incremental_backups, 'install_cron' => $install_cron, 'compression_command' => $compression_command, 'compression_extension' => $compression_extension, } }) } diff --git a/manifests/server/monitor.pp b/manifests/server/monitor.pp index 1e9d168..667ebeb 100644 --- a/manifests/server/monitor.pp +++ b/manifests/server/monitor.pp @@ -1,31 +1,31 @@ # @summary # This is a helper class to add a monitoring user to the database # # @param mysql_monitor_username # The username to create for MySQL monitoring. # @param mysql_monitor_password # The password to create for MySQL monitoring. # @param mysql_monitor_hostname # The hostname from which the monitoring user requests are allowed access. # class mysql::server::monitor ( $mysql_monitor_username = '', - $mysql_monitor_password = '', + Optional[Variant[String, Sensitive[String]]] $mysql_monitor_password = '', $mysql_monitor_hostname = '' ) { Anchor['mysql::server::end'] -> Class['mysql::server::monitor'] mysql_user { "${mysql_monitor_username}@${mysql_monitor_hostname}": ensure => present, password_hash => mysql::password($mysql_monitor_password), require => Class['mysql::server::service'], } mysql_grant { "${mysql_monitor_username}@${mysql_monitor_hostname}/*.*": ensure => present, user => "${mysql_monitor_username}@${mysql_monitor_hostname}", table => '*.*', privileges => ['PROCESS', 'SUPER'], require => Mysql_user["${mysql_monitor_username}@${mysql_monitor_hostname}"], } } diff --git a/manifests/server/root_password.pp b/manifests/server/root_password.pp index 9710e2b..470fd78 100644 --- a/manifests/server/root_password.pp +++ b/manifests/server/root_password.pp @@ -1,57 +1,69 @@ -# @summary +# @summary # Private class for managing the root password # # @api private # class mysql::server::root_password { + if $mysql::server::root_password =~ Sensitive { + $root_password = $mysql::server::root_password.unwrap + } else { + $root_password = $mysql::server::root_password + } + if $root_password == 'UNSET' { + $root_password_set = false + } else { + $root_password_set = true + } + $options = $mysql::server::_options $secret_file = $mysql::server::install_secret_file $login_file = $mysql::server::login_file # New installations of MySQL will configure a default random password for the root user # with an expiration. No actions can be performed until this password is changed. The # below exec will remove this default password. If the user has supplied a root # password it will be set further down with the mysql_user resource. $rm_pass_cmd = join([ "mysqladmin -u root --password=\$(grep -o '[^ ]\\+\$' ${secret_file}) password ''", "rm -f ${secret_file}", ], ' && ') exec { 'remove install pass': command => $rm_pass_cmd, onlyif => "test -f ${secret_file}", path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', } # manage root password if it is set - if $mysql::server::create_root_user == true and $mysql::server::root_password != 'UNSET' { + if $mysql::server::create_root_user and $root_password_set { mysql_user { 'root@localhost': ensure => present, password_hash => mysql::password($mysql::server::root_password), require => Exec['remove install pass'], } } - if $mysql::server::create_root_my_cnf == true and $mysql::server::root_password != 'UNSET' { + if $mysql::server::create_root_my_cnf and $root_password_set { + # TODO: use EPP instead of ERB, as EPP can handle Data of Type Sensitive without further ado file { "${::root_home}/.my.cnf": content => template('mysql/my.cnf.pass.erb'), owner => 'root', mode => '0600', } # show_diff was added with puppet 3.0 if versioncmp($::puppetversion, '3.0') >= 0 { File["${::root_home}/.my.cnf"] { show_diff => false } } - if $mysql::server::create_root_user == true { + if $mysql::server::create_root_user { Mysql_user['root@localhost'] -> File["${::root_home}/.my.cnf"] } } - if $mysql::server::create_root_login_file == true and $mysql::server::root_password != 'UNSET' { + if $mysql::server::create_root_login_file and $root_password_set { file { "${::root_home}/.mylogin.cnf": source => $login_file, owner => 'root', mode => '0600', } } } diff --git a/spec/classes/mysql_server_spec.rb b/spec/classes/mysql_server_spec.rb index 3edcd57..e354f0c 100644 --- a/spec/classes/mysql_server_spec.rb +++ b/spec/classes/mysql_server_spec.rb @@ -1,298 +1,321 @@ # frozen_string_literal: true require 'spec_helper' describe 'mysql::server' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(root_home: '/root') end context 'with defaults' do it { is_expected.to contain_class('mysql::server::install') } it { is_expected.to contain_class('mysql::server::config') } it { is_expected.to contain_class('mysql::server::service') } it { is_expected.to contain_class('mysql::server::root_password') } it { is_expected.to contain_class('mysql::server::providers') } end context 'with remove_default_accounts set' do let(:params) { { remove_default_accounts: true } } it { is_expected.to contain_class('mysql::server::account_security') } end context 'when not managing config file' do let(:params) { { manage_config_file: false } } it { is_expected.to compile.with_all_deps } end context 'when not managing the service' do let(:params) { { service_manage: false } } it { is_expected.to compile.with_all_deps } it { is_expected.not_to contain_service('mysqld') } end context 'configuration options' do context 'when specifying both $override_options and $options' do let(:params) do { override_options: { 'mysqld' => { 'datadir' => '/tmp' } }, options: { 'mysqld' => { 'max_allowed_packet' => '12M' } }, } end it { is_expected.to compile.and_raise_error(%r{You can't specify \$options and \$override_options simultaneously, see the README section 'Customize server options'!}) } end context 'when specifying $options' do let(:params) do { options: { 'mysqld' => { 'datadir' => '/tmp' } }, } end it { is_expected.to compile.with_all_deps } it { is_expected.to contain_mysql_datadir('/tmp') } it { is_expected.not_to contain_mysql_bind_addr('127.0.0.1') } end end context 'mysql::server::install' do it 'contains the package by default' do is_expected.to contain_package('mysql-server').with(ensure: :present) end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_package('mysql-server') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.not_to contain_package('mysql-server') } end context 'with datadir overridden' do let(:params) { { override_options: { 'mysqld' => { 'datadir' => '/tmp' } } } } it { is_expected.to contain_mysql_datadir('/tmp') } end context 'with package provider' do let(:params) do { package_provider: 'dpkg', package_source: '/somewhere', } end it do is_expected.to contain_package('mysql-server').with( provider: 'dpkg', source: '/somewhere', ) end end end context 'mysql::server::service' do context 'with defaults' do it { is_expected.to contain_service('mysqld') } end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_service('mysqld').that_requires('Package[mysql-server]') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.to contain_service('mysqld') } it { is_expected.not_to contain_service('mysqld').that_requires('Package[mysql-server]') } end context 'service_enabled set to false' do let(:params) { { service_enabled: false } } it do is_expected.to contain_service('mysqld').with(ensure: :stopped) end context 'with package_manage set to true' do let(:params) { { package_manage: true } } it { is_expected.to contain_package('mysql-server') } end context 'with package_manage set to false' do let(:params) { { package_manage: false } } it { is_expected.not_to contain_package('mysql-server') } end context 'with datadir overridden' do let(:params) { { override_options: { 'mysqld' => { 'datadir' => '/tmp' } } } } it { is_expected.to contain_mysql_datadir('/tmp') } end end context 'with log-error overridden' do let(:params) { { override_options: { 'mysqld' => { 'log-error' => '/tmp/error.log' } } } } it { is_expected.to contain_file('/tmp/error.log') } end context 'default bind-address' do it { is_expected.to contain_file('mysql-config-file').with_content(%r{^bind-address = 127.0.0.1}) } end context 'with defined bind-address' do let(:params) { { override_options: { 'mysqld' => { 'bind-address' => '1.1.1.1' } } } } it { is_expected.to contain_file('mysql-config-file').with_content(%r{^bind-address = 1.1.1.1}) } end context 'without bind-address' do let(:params) { { override_options: { 'mysqld' => { 'bind-address' => :undef } } } } it { is_expected.to contain_file('mysql-config-file').without_content(%r{^bind-address}) } end end context 'mysql::server::root_password' do describe 'when defaults' do it { is_expected.to contain_exec('remove install pass').with( command: 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /.mysql_secret) password \'\' && rm -f /.mysql_secret', onlyif: 'test -f /.mysql_secret', path: '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', ) } it { is_expected.not_to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when root_password set' do let(:params) { { root_password: 'SET' } } it { is_expected.to contain_mysql_user('root@localhost') } if Puppet.version.to_f >= 3.0 it { is_expected.to contain_file('/root/.my.cnf').with(show_diff: false).that_requires('Mysql_user[root@localhost]') } else it { is_expected.to contain_file('/root/.my.cnf').that_requires('Mysql_user[root@localhost]') } end end describe 'when root_password set, create_root_user set to false' do let(:params) { { root_password: 'SET', create_root_user: false } } it { is_expected.not_to contain_mysql_user('root@localhost') } if Puppet.version.to_f >= 3.0 it { is_expected.to contain_file('/root/.my.cnf').with(show_diff: false) } else it { is_expected.to contain_file('/root/.my.cnf') } end end describe 'when root_password set, create_root_my_cnf set to false' do let(:params) { { root_password: 'SET', create_root_my_cnf: false } } it { is_expected.to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when root_password set, create_root_user and create_root_my_cnf set to false' do let(:params) { { root_password: 'SET', create_root_user: false, create_root_my_cnf: false } } it { is_expected.not_to contain_mysql_user('root@localhost') } it { is_expected.not_to contain_file('/root/.my.cnf') } end describe 'when install_secret_file set to /root/.mysql_secret' do let(:params) { { install_secret_file: '/root/.mysql_secret' } } it { is_expected.to contain_exec('remove install pass').with( command: 'mysqladmin -u root --password=$(grep -o \'[^ ]\\+$\' /root/.mysql_secret) password \'\' && rm -f /root/.mysql_secret', onlyif: 'test -f /root/.mysql_secret', ) } end end context 'mysql::server::providers' do describe 'with users' do let(:params) do { users: { 'foo@localhost' => { 'max_connections_per_hour' => '1', 'max_queries_per_hour' => '2', 'max_updates_per_hour' => '3', 'max_user_connections' => '4', 'password_hash' => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', }, 'foo2@localhost' => {}, } } end it { is_expected.to contain_mysql_user('foo@localhost').with( max_connections_per_hour: '1', max_queries_per_hour: '2', max_updates_per_hour: '3', max_user_connections: '4', password_hash: '*F3A2A51A9B0F2BE2468926B4132313728C250DBF' ) } it { is_expected.to contain_mysql_user('foo2@localhost').with( max_connections_per_hour: nil, max_queries_per_hour: nil, max_updates_per_hour: nil, max_user_connections: nil, password_hash: nil ) } end + describe 'with users and Sensitive password_hash' do + let(:params) do + { users: { + 'foo@localhost' => { + 'max_connections_per_hour' => '1', + 'max_queries_per_hour' => '2', + 'max_updates_per_hour' => '3', + 'max_user_connections' => '4', + 'password_hash' => sensitive('*F3A2A51A9B0F2BE2468926B4132313728C250DBF'), + }, + 'foo2@localhost' => {}, + } } + end + + it { + is_expected.to contain_mysql_user('foo@localhost').with( + max_connections_per_hour: '1', max_queries_per_hour: '2', + max_updates_per_hour: '3', max_user_connections: '4', + password_hash: 'Sensitive [value redacted]' + ) + } + end + describe 'with grants' do let(:params) do { grants: { 'foo@localhost/somedb.*' => { 'user' => 'foo@localhost', 'table' => 'somedb.*', 'privileges' => ['SELECT', 'UPDATE'], 'options' => ['GRANT'], }, 'foo2@localhost/*.*' => { 'user' => 'foo2@localhost', 'table' => '*.*', 'privileges' => ['SELECT'], }, } } end it { is_expected.to contain_mysql_grant('foo@localhost/somedb.*').with( user: 'foo@localhost', table: 'somedb.*', privileges: ['SELECT', 'UPDATE'], options: ['GRANT'] ) } it { is_expected.to contain_mysql_grant('foo2@localhost/*.*').with( user: 'foo2@localhost', table: '*.*', privileges: ['SELECT'], options: nil ) } end describe 'with databases' do let(:params) do { databases: { 'somedb' => { 'charset' => 'latin1', 'collate' => 'latin1', }, 'somedb2' => {}, } } end it { is_expected.to contain_mysql_database('somedb').with( charset: 'latin1', collate: 'latin1', ) } it { is_expected.to contain_mysql_database('somedb2') } end end end end # rubocop:enable RSpec/NestedGroups end diff --git a/spec/functions/mysql_password_spec.rb b/spec/functions/mysql_password_spec.rb index dbe4441..e4e6c69 100644 --- a/spec/functions/mysql_password_spec.rb +++ b/spec/functions/mysql_password_spec.rb @@ -1,43 +1,53 @@ # frozen_string_literal: true require 'spec_helper' shared_examples 'mysql::password function' do it 'exists' do is_expected.not_to eq(nil) end it 'raises a ArgumentError if there is less than 1 arguments' do is_expected.to run.with_params.and_raise_error(ArgumentError) end - it 'raises a ArgumentError if there is more than 1 arguments' do - is_expected.to run.with_params('foo', 'bar').and_raise_error(ArgumentError) + it 'raises a ArgumentError if there is more than 2 arguments' do + is_expected.to run.with_params('foo', false, 'bar').and_raise_error(ArgumentError) end it 'converts password into a hash' do is_expected.to run.with_params('password').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') end + it 'accept password as Sensitive' do + is_expected.to run.with_params(sensitive('password')).and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') + end + + # Test of a Returnvalue of Datatype Sensitive does not work + it 'returns Sensitive with sensitive=true' do + pending 'should have a Returnvalue of Datatype Sensitive' + is_expected.to run.with_params('password', true).and_return(sensitive('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) + end + it 'password should be String' do is_expected.to run.with_params(123).and_raise_error(ArgumentError) end it 'converts an empty password into a empty string' do is_expected.to run.with_params('').and_return('') end it 'does not convert a password that is already a hash' do is_expected.to run.with_params('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') end end describe 'mysql::password' do it_behaves_like 'mysql::password function' describe 'non-namespaced shim' do describe 'mysql_password', type: :puppet_function do it_behaves_like 'mysql::password function' end end end diff --git a/templates/my.cnf.pass.erb b/templates/my.cnf.pass.erb index b82cca3..9f9d038 100644 --- a/templates/my.cnf.pass.erb +++ b/templates/my.cnf.pass.erb @@ -1,11 +1,11 @@ ### MANAGED BY PUPPET ### <% %w(mysql client mysqldump mysqladmin mysqlcheck).each do |section| %> [<%= section -%>] user=root host=localhost -<% unless scope.lookupvar('mysql::server::root_password') == 'UNSET' -%> -password='<%= scope.lookupvar('mysql::server::root_password') %>' +<% if @root_password_set -%> +password='<%= @root_password %>' <% end -%> socket=<%= @options['client']['socket'] %> <% end %> diff --git a/templates/mysqlbackup.sh.erb b/templates/mysqlbackup.sh.erb index aea8cae..d90d567 100755 --- a/templates/mysqlbackup.sh.erb +++ b/templates/mysqlbackup.sh.erb @@ -1,125 +1,125 @@ <%- if @kernel == 'Linux' -%> #!/bin/bash <%- else -%> #!/bin/sh <%- end -%> # # MySQL Backup Script # Dumps mysql databases to a file for another backup tool to pick up. # # MySQL code: # GRANT SELECT, RELOAD, LOCK TABLES ON *.* TO 'user'@'localhost' # IDENTIFIED BY 'password'; # FLUSH PRIVILEGES; # ##### START CONFIG ################################################### USER=<%= @backupuser %> -PASS='<%= @backuppassword %>' +PASS='<%= @backuppassword_unsensitive %>' MAX_ALLOWED_PACKET=<%= @maxallowedpacket %> DIR=<%= @backupdir %> ROTATE=<%= [ Integer(@backuprotate) - 1, 0 ].max %> # Create temporary mysql cnf file. TMPFILE=`mktemp /tmp/backup.XXXXXX` || exit 1 <%- if @kernel == 'SunOS' -%> echo "[client]\npassword=$PASS\nuser=$USER\nmax_allowed_packet=$MAX_ALLOWED_PACKET" > $TMPFILE <%- else -%> echo -e "[client]\npassword=$PASS\nuser=$USER\nmax_allowed_packet=$MAX_ALLOWED_PACKET" > $TMPFILE <%- end -%> <% if @prescript -%> <%- [@prescript].flatten.compact.each do |script|%> <%= script %> <%- end -%> <% end -%> # Ensure backup directory exist. mkdir -p $DIR PREFIX=mysql_backup_ <% if @ignore_events %> ADDITIONAL_OPTIONS="--ignore-table=mysql.event" <% else %> ADDITIONAL_OPTIONS="--events" <% end %> <%# Only include routines or triggers if we're doing a file per database -%> <%# backup. This happens if we named databases, or if we explicitly set -%> <%# file per database mode -%> <% if !@backupdatabases.empty? || @file_per_database -%> <% if @include_triggers -%> ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --triggers" <% else -%> ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-triggers" <% end -%> <% if @include_routines -%> ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --routines" <% else -%> ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-routines" <% end -%> <% end -%> <%- if @optional_args and @optional_args.is_a?(Array) -%> <%- @optional_args.each do |arg| -%> ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS <%= arg %>" <%- end -%> <%- end -%> ##### STOP CONFIG #################################################### PATH=<%= @execpath %> <%- if @kernel == 'Linux' -%> set -o pipefail <%- end -%> cleanup() { <%- if @kernel == 'SunOS' -%> gfind "${DIR}/" -maxdepth 1 -type f -name "${PREFIX}*.sql*" -mtime +${ROTATE} -print0 | gxargs -0 -r rm -f <%- else -%> find "${DIR}/" -maxdepth 1 -type f -name "${PREFIX}*.sql*" -mtime +${ROTATE} -print0 | xargs -0 -r rm -f <%- end -%> } <% if @delete_before_dump -%> cleanup <% end -%> <% if @backupdatabases.empty? -%> <% if @file_per_database -%> mysql --defaults-extra-file=$TMPFILE -s -r -N -e 'SHOW DATABASES' | while read dbname do <%= @backupmethod -%> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ ${ADDITIONAL_OPTIONS} \ ${dbname} <% if @backupcompress %>| <%= @compression_command %> <% end %>> ${DIR}/${PREFIX}${dbname}_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %><%= @compression_extension %><% end %> done <% else -%> <%= @backupmethod -%> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ ${ADDITIONAL_OPTIONS} \ --all-databases <% if @backupcompress %>| <%= @compression_command %> <% end %>> ${DIR}/${PREFIX}`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %><%= @compression_extension %><% end %> <% end -%> <% else -%> <% @backupdatabases.each do |db| -%> <%= @backupmethod -%> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ ${ADDITIONAL_OPTIONS} \ <%= db %><% if @backupcompress %>| <%= @compression_command %> <% end %>> ${DIR}/${PREFIX}<%= db %>_`date +%Y%m%d-%H%M%S`.sql<% if @backupcompress %><%= @compression_extension %><% end %> <% end -%> <% end -%> <% unless @delete_before_dump -%> if [ $? -eq 0 ] ; then cleanup touch <%= @backup_success_file_path %> fi <% end -%> <% if @postscript -%> <%- [@postscript].flatten.compact.each do |script|%> <%= script %> <%- end -%> <% end -%> # Remove temporary file rm -f $TMPFILE diff --git a/templates/xtrabackup.sh.erb b/templates/xtrabackup.sh.erb index 0f7a98c..c3d66bf 100644 --- a/templates/xtrabackup.sh.erb +++ b/templates/xtrabackup.sh.erb @@ -1,75 +1,75 @@ <%- if @kernel == 'Linux' -%> #!/bin/bash <%- else -%> #!/bin/sh <%- end -%> # # A wrapper for Xtrabackup ROTATE=<%= [ Integer(@backuprotate) - 1, 0 ].max %> DIR=<%= @backupdir %> # Ensure backup directory exist. mkdir -p $DIR <%- if @kernel == 'Linux' -%> set -o pipefail <%- end -%> <% if @prescript -%> <%- [@prescript].flatten.compact.each do |script| %> <%= script %> <%- end -%> <% end -%> cleanup() { <%- if @kernel == 'SunOS' -%> gfind "${DIR}/" -mindepth 1 -maxdepth 1 -mtime +${ROTATE} -print0 | gxargs -0 -r rm -rf <%- else -%> find "${DIR}/" -mindepth 1 -maxdepth 1 -mtime +${ROTATE} -print0 | xargs -0 -r rm -rf <%- end -%> } <% if @delete_before_dump -%> cleanup <% end -%> <%- _innobackupex_args = '' -%> -<%- if @backupuser and @backuppassword -%> - <%- _innobackupex_args = '--user="' + @backupuser + '" --password="' + @backuppassword + '"' -%> +<%- if @backupuser and @backuppassword_unsensitive -%> + <%- _innobackupex_args = '--user="' + @backupuser + '" --password="' + @backuppassword_unsensitive + '"' -%> <%- end -%> <%- if @backupcompress -%> <%- _innobackupex_args = _innobackupex_args + ' --compress' -%> <%- end -%> <%- if @backupdatabases and @backupdatabases.is_a?(Array) and !@backupdatabases.empty? -%> <%- _innobackupex_args = _innobackupex_args + ' --databases="' + @backupdatabases.join(' ') + '"' -%> <%- end -%> <%- if @optional_args and @optional_args.is_a?(Array) -%> <%- @optional_args.each do |arg| -%> <%- _innobackupex_args = _innobackupex_args + ' ' + arg -%> <%- end -%> <%- end -%> <%= @backupmethod -%> <%= _innobackupex_args %> $@ <% unless @delete_before_dump -%> if [ $? -eq 0 ] ; then cleanup touch <%= @backup_success_file_path %> fi <% end -%> <% if @postscript -%> <%- [@postscript].flatten.compact.each do |script| %> <%= script %> <%- end -%> <% end -%>