From 305b0d2a87da836201100cacf6f8220129f7e889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Eeden?= Date: Sun, 11 Jan 2015 11:07:37 +0100 Subject: [PATCH] Support authentication plugins This uses CREATE USER xxx IDENTIFIED WITH yyy For tests: unix_socket is not loaded by default, so this might require: install plugin unix_socket soname 'auth_socket.so'; The mysql_native_password plugin is available by default and allows you to also set a password. --- lib/puppet/provider/mysql_user/mysql.rb | 24 +++++++++++++------ lib/puppet/type/mysql_user.rb | 5 ++++ .../puppet/provider/mysql_user/mysql_spec.rb | 7 +++--- tests/mysql_user.pp | 11 +++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/puppet/provider/mysql_user/mysql.rb b/lib/puppet/provider/mysql_user/mysql.rb index 066ea0b00..cc6f40a28 100644 --- a/lib/puppet/provider/mysql_user/mysql.rb +++ b/lib/puppet/provider/mysql_user/mysql.rb @@ -12,13 +12,14 @@ def self.instances # To reduce the number of calls to MySQL we collect all the properties in # one big swoop. users.collect do |name| - query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" + query = "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{name}'" @max_user_connections, @max_connections_per_hour, @max_queries_per_hour, - @max_updates_per_hour, @password = mysql([defaults_file, "-NBe", query].compact).split(/\s/) + @max_updates_per_hour, @password, @plugin = mysql([defaults_file, "-NBe", query].compact).split(/\s/) 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, @@ -39,17 +40,26 @@ def self.prefetch(resources) end def create - merged_name = @resource[:name].sub('@', "'@'") + merged_name = @resource[:name].sub('@', "'@'") 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 - mysql([defaults_file, '-e', "GRANT USAGE ON *.* TO '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}' 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}"].compact) - - @property_hash[:ensure] = :present - @property_hash[:password_hash] = password_hash + # 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? + mysql([defaults_file, '-e', "CREATE USER '#{merged_name}' IDENTIFIED WITH '#{plugin}'"].compact) + @property_hash[:ensure] = :present + @property_hash[:plugin] = plugin + else + mysql([defaults_file, '-e', "CREATE USER '#{merged_name}' IDENTIFIED BY PASSWORD '#{password_hash}'"].compact) + @property_hash[:ensure] = :present + @property_hash[:password_hash] = password_hash + end + mysql([defaults_file, '-e', "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}"].compact) @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 diff --git a/lib/puppet/type/mysql_user.rb b/lib/puppet/type/mysql_user.rb index c408ccd12..bac32d5d7 100644 --- a/lib/puppet/type/mysql_user.rb +++ b/lib/puppet/type/mysql_user.rb @@ -40,6 +40,11 @@ newvalue(/\w+/) end + newproperty(:plugin) do + desc 'The authentication plugin of the user.' + newvalue(/\w+/) + end + newproperty(:max_user_connections) do desc "Max concurrent connections for the user. 0 means no (or global) limit." newvalue(/\d+/) diff --git a/spec/unit/puppet/provider/mysql_user/mysql_spec.rb b/spec/unit/puppet/provider/mysql_user/mysql_spec.rb index dacbae4b0..446c6a920 100644 --- a/spec/unit/puppet/provider/mysql_user/mysql_spec.rb +++ b/spec/unit/puppet/provider/mysql_user/mysql_spec.rb @@ -37,7 +37,7 @@ Puppet::Util.stubs(:which).with('mysql').returns('/usr/bin/mysql') File.stubs(:file?).with('/root/.my.cnf').returns(true) provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns('joe@localhost') - provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'"]).returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = 'joe@localhost'"]).returns('10 10 10 10 *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4') end let(:instance) { provider.class.instances.first } @@ -46,7 +46,7 @@ it 'returns an array of users' do provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT CONCAT(User, '@',Host) AS User FROM mysql.user"]).returns(raw_users) parsed_users.each do |user| - provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') + provider.class.stubs(:mysql).with([defaults_file, '-NBe', "SELECT MAX_USER_CONNECTIONS, MAX_CONNECTIONS, MAX_QUESTIONS, MAX_UPDATES, PASSWORD, PLUGIN FROM mysql.user WHERE CONCAT(user, '@', host) = '#{user}'"]).returns('10 10 10 10 ') end usernames = provider.class.instances.collect {|x| x.name } @@ -63,7 +63,8 @@ describe 'create' do it 'makes a user' do - provider.expects(:mysql).with([defaults_file, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' IDENTIFIED BY PASSWORD '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10"]) + provider.expects(:mysql).with([defaults_file, '-e', "CREATE USER 'joe'@'localhost' IDENTIFIED BY PASSWORD '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'"]) + provider.expects(:mysql).with([defaults_file, '-e', "GRANT USAGE ON *.* TO 'joe'@'localhost' WITH MAX_USER_CONNECTIONS 10 MAX_CONNECTIONS_PER_HOUR 10 MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 10"]) provider.expects(:exists?).returns(true) expect(provider.create).to be_truthy end diff --git a/tests/mysql_user.pp b/tests/mysql_user.pp index 7490d33b5..893a03e31 100644 --- a/tests/mysql_user.pp +++ b/tests/mysql_user.pp @@ -19,3 +19,14 @@ ensure => present, password_hash => mysql_password('blah'), } + +mysql_user{ 'socketplugin@%': + ensure => present, + plugin => 'unix_socket', +} + +mysql_user{ 'socketplugin@%': + ensure => present, + password_hash => mysql_password('blah'), + plugin => 'mysql_native_password', +}