diff --git a/lib/puppet/provider/mysql_grant/mysql.rb b/lib/puppet/provider/mysql_grant/mysql.rb index b3e97b4..bed2cd2 100644 --- a/lib/puppet/provider/mysql_grant/mysql.rb +++ b/lib/puppet/provider/mysql_grant/mysql.rb @@ -1,185 +1,202 @@ # frozen_string_literal: true require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) Puppet::Type.type(:mysql_grant).provide(:mysql, parent: Puppet::Provider::Mysql) do desc 'Set grants for users in MySQL.' commands mysql_raw: 'mysql' def self.instances - instances = [] + instance_configs = {} users.map do |user| user_string = cmd_user(user) query = "SHOW GRANTS FOR #{user_string};" begin grants = mysql_caller(query, 'regular') rescue Puppet::ExecutionFailure => e # Silently ignore users with no grants. Can happen e.g. if user is # defined with fqdn and server is run with skip-name-resolve. Example: # Default root user created by mysql_install_db on a host with fqdn # of myhost.mydomain.my: root@myhost.mydomain.my, when MySQL is started # with --skip-name-resolve. next if %r{There is no such grant defined for user}.match?(e.inspect) raise Puppet::Error, _('#mysql had an error -> %{inspect}') % { inspect: e.inspect } end # Once we have the list of grants generate entries for each. grants.each_line do |grant| # Match the munges we do in the type. munged_grant = grant.delete("'").delete('`').delete('"') # Matching: GRANT (SELECT, UPDATE) PRIVILEGES ON (*.*) TO ('root')@('127.0.0.1') (WITH GRANT OPTION) next unless match = munged_grant.match(%r{^GRANT\s(.+)\sON\s(.+)\sTO\s(.*)@(.*?)(\s.*)?$}) # rubocop:disable Lint/AssignmentInCondition privileges, table, user, host, rest = match.captures table.gsub!('\\\\', '\\') # split on ',' if it is not a non-'('-containing string followed by a # closing parenthesis ')'-char - e.g. only split comma separated elements not in # parentheses stripped_privileges = privileges.strip.split(%r{\s*,\s*(?![^(]*\))}).map do |priv| # split and sort the column_privileges in the parentheses and rejoin if priv.include?('(') type, col = priv.strip.split(%r{\s+|\b}, 2) type.upcase + ' (' + col.slice(1...-1).strip.split(%r{\s*,\s*}).sort.join(', ') + ')' else # Once we split privileges up on the , we need to make sure we # shortern ALL PRIVILEGES to just all. (priv == 'ALL PRIVILEGES') ? 'ALL' : priv.strip end end - sorted_privileges = stripped_privileges.sort - if newer_than('mysql' => '8.0.0') && sorted_privileges == ['ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROLE', 'CREATE ROUTINE', 'CREATE TABLESPACE', 'CREATE TEMPORARY TABLES', 'CREATE USER', - 'CREATE VIEW', 'DELETE', 'DROP', 'DROP ROLE', 'EVENT', 'EXECUTE', 'FILE', 'INDEX', 'INSERT', 'LOCK TABLES', 'PROCESS', 'REFERENCES', - 'RELOAD', 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'SELECT', 'SHOW DATABASES', 'SHOW VIEW', 'SHUTDOWN', 'SUPER', 'TRIGGER', - 'UPDATE'] - sorted_privileges = ['ALL'] - end # Same here, but to remove OPTION leaving just GRANT. options = if %r{WITH\sGRANT\sOPTION}.match?(rest) ['GRANT'] else ['NONE'] end # fix double backslash that MySQL prints, so resources match table.gsub!('\\\\', '\\') # We need to return an array of instances so capture these - instances << new( - name: "#{user}@#{host}/#{table}", - ensure: :present, + name = "#{user}@#{host}/#{table}" + if instance_configs.key?(name) + instance_config = instance_configs[name] + stripped_privileges.concat instance_config[:privileges] + options.concat instance_config[:options] + end + + sorted_privileges = stripped_privileges.uniq.sort + if newer_than('mysql' => '8.0.0') && sorted_privileges == ['ALTER', 'ALTER ROUTINE', 'CREATE', 'CREATE ROLE', 'CREATE ROUTINE', 'CREATE TABLESPACE', 'CREATE TEMPORARY TABLES', 'CREATE USER', + 'CREATE VIEW', 'DELETE', 'DROP', 'DROP ROLE', 'EVENT', 'EXECUTE', 'FILE', 'INDEX', 'INSERT', 'LOCK TABLES', 'PROCESS', 'REFERENCES', + 'RELOAD', 'REPLICATION CLIENT', 'REPLICATION SLAVE', 'SELECT', 'SHOW DATABASES', 'SHOW VIEW', 'SHUTDOWN', 'SUPER', 'TRIGGER', + 'UPDATE'] + sorted_privileges = ['ALL'] + end + + instance_configs[name] = { privileges: sorted_privileges, table: table, user: "#{user}@#{host}", - options: options, - ) + options: options.uniq, + } end end + instances = [] + instance_configs.map do |name, config| + instances << new( + name: name, + ensure: :present, + privileges: config[:privileges], + table: config[:table], + user: config[:user], + options: config[:options], + ) + end instances end def self.prefetch(resources) users = instances resources.each_key do |name| if provider = users.find { |user| user.name == name } # rubocop:disable Lint/AssignmentInCondition resources[name].provider = provider end end end def grant(user, table, privileges, options) user_string = self.class.cmd_user(user) priv_string = self.class.cmd_privs(privileges) table_string = privileges.include?('PROXY') ? self.class.cmd_user(table) : self.class.cmd_table(table) query = "GRANT #{priv_string}" query += " ON #{table_string}" query += " TO #{user_string}" query += self.class.cmd_options(options) unless options.nil? self.class.mysql_caller(query, 'system') end def create grant(@resource[:user], @resource[:table], @resource[:privileges], @resource[:options]) @property_hash[:ensure] = :present @property_hash[:table] = @resource[:table] @property_hash[:user] = @resource[:user] @property_hash[:options] = @resource[:options] if @resource[:options] @property_hash[:privileges] = @resource[:privileges] exists? ? (return true) : (return false) end def revoke(user, table, revoke_privileges = ['ALL']) user_string = self.class.cmd_user(user) table_string = revoke_privileges.include?('PROXY') ? self.class.cmd_user(table) : self.class.cmd_table(table) priv_string = self.class.cmd_privs(revoke_privileges) # revoke grant option needs to be a extra query, because # "REVOKE ALL PRIVILEGES, GRANT OPTION [..]" is only valid mysql syntax # if no ON clause is used. # It hast to be executed before "REVOKE ALL [..]" since a GRANT has to # exist to be executed successfully if revoke_privileges.include?('ALL') && !revoke_privileges.include?('PROXY') query = "REVOKE GRANT OPTION ON #{table_string} FROM #{user_string}" self.class.mysql_caller(query, 'system') end query = "REVOKE #{priv_string} ON #{table_string} FROM #{user_string}" self.class.mysql_caller(query, 'system') end def destroy # if the user was dropped, it'll have been removed from the user hash # as the grants are already removed by the DROP statement if self.class.users.include? @property_hash[:user] if @property_hash[:privileges].include?('PROXY') revoke(@property_hash[:user], @property_hash[:table], @property_hash[:privileges]) else revoke(@property_hash[:user], @property_hash[:table]) end end @property_hash.clear exists? ? (return false) : (return true) end def exists? @property_hash[:ensure] == :present || false end def flush @property_hash.clear self.class.mysql_caller('FLUSH PRIVILEGES', 'regular') end mk_resource_methods def diff_privileges(privileges_old, privileges_new) diff = { revoke: [], grant: [] } if privileges_old.include? 'ALL' diff[:revoke] = privileges_old diff[:grant] = privileges_new elsif privileges_new.include? 'ALL' diff[:grant] = privileges_new else diff[:revoke] = privileges_old - privileges_new diff[:grant] = privileges_new - privileges_old end diff end def privileges=(privileges) diff = diff_privileges(@property_hash[:privileges], privileges) unless diff[:revoke].empty? revoke(@property_hash[:user], @property_hash[:table], diff[:revoke]) end unless diff[:grant].empty? grant(@property_hash[:user], @property_hash[:table], diff[:grant], @property_hash[:options]) end @property_hash[:privileges] = privileges self.privileges end def options=(options) revoke(@property_hash[:user], @property_hash[:table]) grant(@property_hash[:user], @property_hash[:table], @property_hash[:privileges], options) @property_hash[:options] = options self.options end end diff --git a/spec/acceptance/types/mysql_grant_spec.rb b/spec/acceptance/types/mysql_grant_spec.rb index c61cbb3..189e6c1 100644 --- a/spec/acceptance/types/mysql_grant_spec.rb +++ b/spec/acceptance/types/mysql_grant_spec.rb @@ -1,701 +1,718 @@ # frozen_string_literal: true require 'spec_helper_acceptance' describe 'mysql_grant' do before(:all) do pp = <<-MANIFEST class { 'mysql::server': root_password => 'password', } MANIFEST apply_manifest(pp, catch_failures: true) end describe 'missing privileges for user' do pp = <<-MANIFEST mysql_user { 'test1@tester': ensure => present, } mysql_grant { 'test1@tester/test.*': ensure => 'present', table => 'test.*', user => 'test1@tester', require => Mysql_user['test1@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to contain(%r{`privileges` `parameter` is required}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test1@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'test1' on host 'tester'}) end end describe 'missing table for user' do pp = <<-MANIFEST mysql_user { 'atest@tester': ensure => present, } mysql_grant { 'atest@tester/test.*': ensure => 'present', user => 'atest@tester', privileges => ['ALL'], require => Mysql_user['atest@tester'], } MANIFEST it 'fails' do apply_manifest(pp, expect_failures: true) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR atest@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'atest' on host 'tester'}) end end describe 'adding privileges' do pp = <<-MANIFEST mysql_user { 'test2@tester': ensure => present, } mysql_grant { 'test2@tester/test.*': ensure => 'present', table => 'test.*', user => 'test2@tester', privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test2@tester"') expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE.*TO ['|`]test2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding privileges with special character in name' do pp = <<-MANIFEST mysql_user { 'test-2@tester': ensure => present, } mysql_grant { 'test-2@tester/test.*': ensure => 'present', table => 'test.*', user => 'test-2@tester', privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test-2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell("mysql -NBe \"SHOW GRANTS FOR 'test-2'@tester\"") expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE.*TO ['|`]test-2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding option' do pp = <<-MANIFEST mysql_user { 'test3@tester': ensure => present, } mysql_grant { 'test3@tester/test.*': ensure => 'present', table => 'test.*', user => 'test3@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE'], require => Mysql_user['test3@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test3@tester"') expect(result.stdout).to contain(%r{GRANT SELECT, UPDATE ON `test`.* TO ['|`]test3['|`]@['|`]tester['|`] WITH GRANT OPTION$}) expect(result.stderr).to be_empty end end describe 'adding all privileges without table' do pp = <<-MANIFEST mysql_user { 'test4@tester': ensure => present, } mysql_grant { 'test4@tester/test.*': ensure => 'present', user => 'test4@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE', 'ALL'], require => Mysql_user['test4@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to contain(%r{`table` `parameter` is required.}) end end describe 'adding all privileges' do pp = <<-MANIFEST mysql_user { 'test4@tester': ensure => present, } mysql_grant { 'test4@tester/test.*': ensure => 'present', table => 'test.*', user => 'test4@tester', options => ['GRANT'], privileges => ['SELECT', 'UPDATE', 'ALL'], require => Mysql_user['test4@tester'], } MANIFEST it 'onlies try to apply ALL' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test4@tester"') expect(result.stdout).to contain(%r{GRANT ALL PRIVILEGES ON `test`.* TO ['|`]test4['|`]@['|`]tester['|`] WITH GRANT OPTION}) expect(result.stderr).to be_empty end end # Test combinations of user@host to ensure all cases work. describe 'short hostname' do pp = <<-MANIFEST mysql_user { 'test@short': ensure => present, } mysql_grant { 'test@short/test.*': ensure => 'present', table => 'test.*', user => 'test@short', privileges => 'ALL', require => Mysql_user['test@short'], } mysql_user { 'test@long.hostname.com': ensure => present, } mysql_grant { 'test@long.hostname.com/test.*': ensure => 'present', table => 'test.*', user => 'test@long.hostname.com', privileges => 'ALL', require => Mysql_user['test@long.hostname.com'], } mysql_user { 'test@192.168.5.6': ensure => present, } mysql_grant { 'test@192.168.5.6/test.*': ensure => 'present', table => 'test.*', user => 'test@192.168.5.6', privileges => 'ALL', require => Mysql_user['test@192.168.5.6'], } mysql_user { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004': ensure => present, } mysql_grant { 'test@2607:f0d0:1002:0051:0000:0000:0000:0004/test.*': ensure => 'present', table => 'test.*', user => 'test@2607:f0d0:1002:0051:0000:0000:0000:0004', privileges => 'ALL', require => Mysql_user['test@2607:f0d0:1002:0051:0000:0000:0000:0004'], } mysql_user { 'test@::1/128': ensure => present, } mysql_grant { 'test@::1/128/test.*': ensure => 'present', table => 'test.*', user => 'test@::1/128', privileges => 'ALL', require => Mysql_user['test@::1/128'], } MANIFEST it 'applies' do apply_manifest(pp, catch_failures: true) end it 'finds short hostname #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test@short"') expect(result.stdout).to contain(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]short['|`]}) expect(result.stderr).to be_empty end it 'finds long hostname #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'long.hostname.com'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]long.hostname.com['|`]}) expect(r.stderr).to be_empty end end it 'finds ipv4 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.6'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]192.168.5.6['|`]}) expect(r.stderr).to be_empty end end it 'finds ipv6 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'2607:f0d0:1002:0051:0000:0000:0000:0004'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]2607:f0d0:1002:0051:0000:0000:0000:0004['|`]}) expect(r.stderr).to be_empty end end it 'finds short ipv6 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'::1/128'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON ['|`]test['|`].* TO ['|`]test['|`]@['|`]::1\/128['|`]}) expect(r.stderr).to be_empty end end end describe 'complex test' do pp = <<-MANIFEST $dbSubnet = '10.10.10.%' mysql_database { 'foo': ensure => present, charset => '#{fetch_charset}', } exec { 'mysql-create-table': command => '/usr/bin/mysql -NBe "CREATE TABLE foo.bar (name VARCHAR(20))"', environment => "HOME=${::root_home}", unless => '/usr/bin/mysql -NBe "SELECT 1 FROM foo.bar LIMIT 1;"', require => Mysql_database['foo'], } Mysql_grant { ensure => present, options => ['GRANT'], privileges => ['ALL'], table => '*.*', require => [ Mysql_database['foo'], Exec['mysql-create-table'] ], } mysql_user { "user1@${dbSubnet}": ensure => present, } mysql_grant { "user1@${dbSubnet}/*.*": user => "user1@${dbSubnet}", require => Mysql_user["user1@${dbSubnet}"], } mysql_user { "user2@${dbSubnet}": ensure => present, } mysql_grant { "user2@${dbSubnet}/foo.bar": privileges => ['SELECT', 'INSERT', 'UPDATE'], user => "user2@${dbSubnet}", table => 'foo.bar', require => Mysql_user["user2@${dbSubnet}"], } mysql_user { "user3@${dbSubnet}": ensure => present, } mysql_grant { "user3@${dbSubnet}/foo.*": privileges => ['SELECT', 'INSERT', 'UPDATE'], user => "user3@${dbSubnet}", table => 'foo.*', require => Mysql_user["user3@${dbSubnet}"], } mysql_user { 'web@%': ensure => present, } mysql_grant { 'web@%/*.*': user => 'web@%', require => Mysql_user['web@%'], } mysql_user { "web@${dbSubnet}": ensure => present, } mysql_grant { "web@${dbSubnet}/*.*": user => "web@${dbSubnet}", require => Mysql_user["web@${dbSubnet}"], } mysql_user { "web@${::networking['ip']}": ensure => present, } mysql_grant { "web@${::networking['ip']}/*.*": user => "web@${::networking['ip']}", require => Mysql_user["web@${::networking['ip']}"], } mysql_user { 'web@localhost': ensure => present, } mysql_grant { 'web@localhost/*.*': user => 'web@localhost', require => Mysql_user['web@localhost'], } MANIFEST it 'setup mysql::server' do idempotent_apply(pp) end end describe 'lower case privileges' do pp_one = <<-MANIFEST mysql_user { 'lowercase@localhost': ensure => present, } mysql_grant { 'lowercase@localhost/*.*': user => 'lowercase@localhost', privileges => ['SELECT', 'INSERT', 'UPDATE'], table => '*.*', require => Mysql_user['lowercase@localhost'], } MANIFEST it "create ['SELECT', 'INSERT', 'UPDATE'] privs" do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'lowercase@localhost': ensure => present, } mysql_grant { 'lowercase@localhost/*.*': user => 'lowercase@localhost', privileges => ['select', 'insert', 'update'], table => '*.*', require => Mysql_user['lowercase@localhost'], } MANIFEST it "create lowercase ['select', 'insert', 'update'] privs" do apply_manifest(pp_two, catch_changes: true) end end describe 'adding procedure privileges' do pp = <<-MANIFEST exec { 'simpleproc-create': command => 'mysql --user="root" --password="password" --database=mysql --delimiter="//" -NBe "CREATE PROCEDURE simpleproc (OUT param1 INT) BEGIN SELECT COUNT(*) INTO param1 FROM t; end//"', path => '/usr/bin/', before => Mysql_user['test2@tester'], } mysql_user { 'test2@tester': ensure => present, } mysql_grant { 'test2@tester/PROCEDURE mysql.simpleproc': ensure => 'present', table => 'PROCEDURE mysql.simpleproc', user => 'test2@tester', privileges => ['EXECUTE'], require => Mysql_user['test2@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test2@tester"') expect(result.stdout).to match(%r{GRANT EXECUTE ON PROCEDURE `mysql`.`simpleproc` TO ['|`]test2['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end end describe 'adding function privileges' do it 'works without errors' do pp = <<-EOS exec { 'simplefunc-create': command => '/usr/bin/mysql --user="root" --password="password" --database=mysql -NBe "CREATE FUNCTION simplefunc (s CHAR(20)) RETURNS CHAR(50) DETERMINISTIC RETURN CONCAT(\\'Hello, \\', s, \\'!\\')"', before => Mysql_user['test3@tester'], } mysql_user { 'test3@tester': ensure => 'present', } mysql_grant { 'test3@tester/FUNCTION mysql.simplefunc': ensure => 'present', table => 'FUNCTION mysql.simplefunc', user => 'test3@tester', privileges => ['EXECUTE'], require => Mysql_user['test3@tester'], } EOS apply_manifest(pp, catch_failures: true) end # rubocop:enable RSpec/ExampleLength it 'finds the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR test3@tester"') expect(result.stdout).to match(%r{GRANT EXECUTE ON FUNCTION `mysql`.`simplefunc` TO ['|`]test3['|`]@['|`]tester['|`]}) expect(result.stderr).to be_empty end # rubocop:enable RSpec/MultipleExpectations end describe 'proxy privilieges' do describe 'adding proxy privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy1@tester': ensure => present, } mysql_grant { 'proxy1@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy1@tester', privileges => ['PROXY'], require => Mysql_user['proxy1@tester'], } MANIFEST it 'works without errors when version greater than 5.5.0' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do run_shell('mysql -NBe "SHOW GRANTS FOR proxy1@tester"') do |r| expect(r.stdout).to match(%r{GRANT PROXY ON 'proxy_user'@'proxy_host' TO ['|`]proxy1['|`]@['|`]tester['|`]}) expect(r.stderr).to be_empty end end end describe 'removing proxy privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy1@tester': ensure => present, } mysql_grant { 'proxy1@tester/proxy_user@proxy_host': ensure => 'absent', table => 'proxy_user@proxy_host', user => 'proxy1@tester', privileges => ['PROXY'], require => Mysql_user['proxy1@tester'], } MANIFEST it 'works without errors' do apply_manifest(pp, catch_failures: true) end it 'finds the user #stdout' do run_shell('mysql -NBe "SHOW GRANTS FOR proxy1@tester"') do |r| expect(r.stdout).not_to match(%r{GRANT PROXY ON 'proxy_user'@'proxy_host' TO ['|`]proxy1['|`]@['|`]tester['|`]}) expect(r.stderr).to be_empty end end end describe 'adding proxy privileges with other privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy2@tester': ensure => present, } mysql_grant { 'proxy2@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy2@tester', privileges => ['PROXY', 'SELECT'], require => Mysql_user['proxy2@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{`privileges` `parameter`: PROXY can only be specified by itself}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy2@tester"', expect_failures: true) expect(result.stderr).to match(%r{There is no such grant defined for user 'proxy2' on host 'tester'}) end end describe 'adding proxy privileges with mysql version less than 5.5.0', unless: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy3@tester': ensure => present, } mysql_grant { 'proxy3@tester/proxy_user@proxy_host': ensure => 'present', table => 'proxy_user@proxy_host', user => 'proxy3@tester', privileges => ['PROXY', 'SELECT'], require => Mysql_user['proxy3@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{PROXY user not supported on mysql versions < 5\.5\.0}i) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy2@tester"', expect_failures: true) expect(result.stderr).to match(%r{There is no such grant defined for user 'proxy2' on host 'tester'}) end end describe 'adding proxy privileges with invalid proxy user', if: Gem::Version.new(mysql_version) > Gem::Version.new('5.5.0') do pp = <<-MANIFEST mysql_user { 'proxy3@tester': ensure => present, } mysql_grant { 'proxy3@tester/invalid_proxy_user': ensure => 'present', table => 'invalid_proxy_user', user => 'proxy3@tester', privileges => ['PROXY'], require => Mysql_user['proxy3@tester'], } MANIFEST it 'fails' do result = apply_manifest(pp, expect_failures: true) expect(result.stderr).to match(%r{`table` `property` for PROXY should be specified as proxy_user@proxy_host.}) end it 'does not find the user' do result = run_shell('mysql -NBe "SHOW GRANTS FOR proxy3@tester"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'proxy3' on host 'tester'}) end end end describe 'grants with skip-name-resolve specified' do pp_one = <<-MANIFEST class { 'mysql::server': override_options => { 'mysqld' => {'skip-name-resolve' => true} }, restart => true, } MANIFEST it 'setup mysql::server' do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'test@fqdn.com': ensure => present, } mysql_grant { 'test@fqdn.com/test.*': ensure => 'present', table => 'test.*', user => 'test@fqdn.com', privileges => 'ALL', require => Mysql_user['test@fqdn.com'], } mysql_user { 'test@192.168.5.7': ensure => present, } mysql_grant { 'test@192.168.5.7/test.*': ensure => 'present', table => 'test.*', user => 'test@192.168.5.7', privileges => 'ALL', require => Mysql_user['test@192.168.5.7'], } MANIFEST it 'applies' do apply_manifest(pp_two, catch_failures: true) end it 'fails with fqdn' do unless Gem::Version.new(mysql_version) > Gem::Version.new('5.7.0') result = run_shell('mysql -NBe "SHOW GRANTS FOR test@fqdn.com"', expect_failures: true) expect(result.stderr).to contain(%r{There is no such grant defined for user 'test' on host 'fqdn.com'}) end end it 'finds ipv4 #stdout' do run_shell("mysql -NBe \"SHOW GRANTS FOR 'test'@'192.168.5.7'\"") do |r| expect(r.stdout).to match(%r{GRANT ALL PRIVILEGES ON `test`.* TO ['|`]test['|`]@['|`]192.168.5.7['|`]}) expect(r.stderr).to be_empty end end pp_three = <<-MANIFEST mysql_user { 'test@fqdn.com': ensure => present, } mysql_grant { 'test@fqdn.com/test.*': ensure => 'present', table => 'test.*', user => 'test@fqdn.com', privileges => 'ALL', require => Mysql_user['test@fqdn.com'], } MANIFEST it 'fails to execute while applying' do mysql_cmd = run_shell('which mysql').stdout.chomp run_shell("mv #{mysql_cmd} #{mysql_cmd}.bak") result = apply_manifest(pp_three, expect_failures: true) expect(result.stderr).to match(%r{Could not find a suitable provider for mysql_grant}) run_shell("mv #{mysql_cmd}.bak #{mysql_cmd}") end pp_four = <<-MANIFEST class { 'mysql::server': restart => true, } MANIFEST it 'reset mysql::server config' do apply_manifest(pp_four, catch_failures: true) end end describe 'adding privileges to specific table' do # Using puppet_apply as a helper pp_one = <<-MANIFEST class { 'mysql::server': override_options => { 'root_password' => 'password' } } MANIFEST it 'setup mysql server' do apply_manifest(pp_one, catch_failures: true) end pp_two = <<-MANIFEST mysql_user { 'test@localhost': ensure => present, } mysql_grant { 'test@localhost/grant_spec_db.grant_spec_table_doesnt_exist': user => 'test@localhost', privileges => ['SELECT'], table => 'grant_spec_db.grant_spec_table_doesnt_exist', require => Mysql_user['test@localhost'], } MANIFEST it 'creates grant on missing table will fail' do result = apply_manifest(pp_two, expect_failures: true) expect(result.stderr).to match(%r{Table 'grant_spec_db\.grant_spec_table_doesnt_exist' doesn't exist}) end pp_three = <<-MANIFEST file { '/tmp/grant_spec_table.sql': ensure => file, content => 'CREATE TABLE grant_spec_table (id int);', before => Mysql::Db['grant_spec_db'], } mysql::db { 'grant_spec_db': user => 'root1', password => 'password', sql => '/tmp/grant_spec_table.sql', charset => #{fetch_charset}, } MANIFEST it 'creates table' do apply_manifest(pp_three, catch_failures: true) end it 'has the table' do result = run_shell("mysql -e 'show tables;' grant_spec_db|grep grant_spec_table") expect(result.exit_code).to be_zero end end + + describe 'multiple lines privileges', if: Gem::Version.new(mysql_version) > Gem::Version.new('8.0.0') && Gem::Version.new(mysql_version) < Gem::Version.new('10.0.0') do + pp = <<-MANIFEST + mysql_user { 'multi@localhost': + ensure => present, + } + mysql_grant { 'multi@localhost/*.*': + user => 'multi@localhost', + privileges => ['SELECT', 'INSERT', 'UPDATE', 'BACKUP_ADMIN', 'FLUSH_TABLES'], + table => '*.*', + require => Mysql_user['multi@localhost'], + } + MANIFEST + it 'check idempotency in MySQL 8' do + idempotent_apply(pp) + end + end end