Skip to content

Commit

Permalink
Support mariadb's ed25519 authentication plugin
Browse files Browse the repository at this point in the history
Support the creation and updating of mysql users when the
authentication plugin is set to ed25519.
  • Loading branch information
dciabrin committed Mar 17, 2020
1 parent 312aca8 commit 9fee592
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 6 deletions.
27 changes: 21 additions & 6 deletions lib/puppet/provider/mysql_user/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,27 @@ def self.instances
## Default ...
# rubocop:disable Metrics/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') ||
# https://jira.mariadb.org/browse/MDEV-16238 https://jira.mariadb.org/browse/MDEV-16774
(newer_than('mariadb' => '10.2.16') && older_than('mariadb' => '10.2.19')) ||
(newer_than('mariadb' => '10.3.8') && older_than('mariadb' => '10.3.11'))
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 = mysql_caller(query, 'regular').split(%r{\s})
@password, @plugin, @authentication_string = mysql_caller(query, 'regular').split(%r{\s})
@tls_options = parse_tls_options(ssl_type, ssl_cipher, x509_issuer, x509_subject)
if newer_than('mariadb' => '10.1.21') && ![nil, '', 'mysql_native_password'].include?(@plugin)
# if an specific auth plugin is used (e.g. ed25519),
# use authentication_string as the hash
@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 in authentication_string
# https://jira.mariadb.org/browse/MDEV-16238 https://jira.mariadb.org/browse/MDEV-16774
@password = @authentication_string
end
# rubocop:enable Metrics/LineLength
new(name: name,
ensure: :present,
Expand Down Expand Up @@ -133,11 +142,15 @@ def exists?

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
self.class.mysql_caller("ALTER USER #{merged_name} IDENTIFIED WITH ed25519 AS '#{string}'", '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 string =~ %r{^\*|^$}
self.class.mysql_caller("ALTER USER #{merged_name} IDENTIFIED WITH mysql_native_password AS '#{string}'", 'system')
Expand Down Expand Up @@ -179,7 +192,9 @@ def max_updates_per_hour=(int)
def plugin=(string)
merged_name = self.class.cmd_user(@resource[:name])

if newer_than('mysql' => '5.7.6', 'percona' => '5.7.6')
if newer_than('mariadb' => '10.1.21') && string == 'ed25519'
sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}' AS '#{@resource[:password_hash]}'"
elsif newer_than('mysql' => '5.7.6', 'percona' => '5.7.6')
sql = "ALTER USER #{merged_name} IDENTIFIED WITH '#{string}'"
sql << " AS '#{@resource[:password_hash]}'" if string == 'mysql_native_password'
else
Expand Down
29 changes: 29 additions & 0 deletions spec/acceptance/types/mysql_user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,35 @@ class { 'mysql::server': }
end
end
end

describe 'using ed25519 authentication plugin', if: Gem::Version.new(mysql_version) > Gem::Version.new('10.1.21') do
it 'works without errors' do
pp = <<-EOS
class { 'mysql::server':
restart => true,
override_options => {
'mysqld' => {
'plugin_load_add' => 'auth_ed25519'
}
}
}
mysql_user { 'ashp@localhost':
plugin => 'ed25519',
password_hash => 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU',
}
Class['::mysql::server'] -> Mysql_user<||>
EOS

idempotent_apply(pp)
end

it 'has the correct plugin' do
run_shell("mysql -NBe \"select plugin from mysql.user where CONCAT(user, '@', host) = 'ashp@localhost'\"") do |r|
expect(r.stdout.rstrip).to eq('ed25519')
expect(r.stderr).to be_empty
end
end
end
# rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations
end

Expand Down
39 changes: 39 additions & 0 deletions spec/unit/puppet/provider/mysql_user/mysql_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
string: '/usr/sbin/mysqld (mysqld 10.0.23-MariaDB-0+deb8u1)',
mysql_type: 'mariadb',
},
'mariadb-10.1.44' =>
{
version: '10.1.44',
string: '/usr/sbin/mysqld (mysqld 10.1.44-MariaDB-1~bionic)',
mysql_type: 'mariadb',
},
'percona-5.5' =>
{
version: '5.5.39',
Expand Down Expand Up @@ -133,6 +139,14 @@
usernames = provider.class.instances.map { |x| x.name }
expect(parsed_users).to match_array(usernames)
end
it 'returns an array of users mariadb >= 10.1.21' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string])
provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users)
parsed_users.each { |user| provider.class.stubs(:mysql_caller).with("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) = '#{user}'", 'regular').returns('10 10 10 10 ') } # rubocop:disable Metrics/LineLength

usernames = provider.class.instances.map { |x| x.name }
expect(parsed_users).to match_array(usernames)
end
it 'returns an array of users percona 5.5' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string])
provider.class.stubs(:mysql_caller).with("SELECT CONCAT(User, '@',Host) AS User FROM mysql.user", 'regular').returns(raw_users)
Expand Down Expand Up @@ -282,6 +296,18 @@
provider.expects(:password_hash).returns('*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5')
provider.password_hash = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'
end
it 'changes the hash to an ed25519 hash mariadb >= 10.1.21' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string])
resource.stubs(:value).with(:plugin).returns('ed25519')
provider.class.expects(:mysql_caller).with("ALTER USER 'joe'@'localhost' IDENTIFIED WITH ed25519 AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') # rubocop:disable Metrics/LineLength
provider.expects(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU')
provider.password_hash = 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'
end
it 'changes the hash to an invalid ed25519 hash mariadb >= 10.1.21' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string])
resource.stubs(:value).with(:plugin).returns('ed25519')
expect { provider.password_hash = 'invalid' }.to raise_error(ArgumentError, 'ed25519 hash should be 43 bytes long.')
end
it 'changes the hash percona-5.5' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['percona-5.5'][:string])
provider.class.expects(:mysql_caller).with("SET PASSWORD FOR 'joe'@'localhost' = '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF5'", 'system').returns('0')
Expand Down Expand Up @@ -335,6 +361,19 @@
end
end
end

context 'ed25519' do
context 'mariadb >= 10.1.21' do
it 'changes the authentication plugin' do
provider.class.instance_variable_set(:@mysqld_version_string, mysql_version_string_hash['mariadb-10.1.44'][:string])
resource.stubs('[]').with(:name).returns("'joe'@'localhost'")
resource.stubs('[]').with(:password_hash).returns('z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU')
provider.class.expects(:mysql_caller).with("ALTER USER ''joe''@''localhost'' IDENTIFIED WITH 'ed25519' AS 'z0pjExBYbzbupUByZRrQvC6kRCcE8n/tC7kUdUD11fU'", 'system').returns('0') # rubocop:disable Metrics/LineLength
provider.expects(:plugin).returns('ed25519')
provider.plugin = 'ed25519'
end
end
end
# rubocop:enable RSpec/NestedGroups
end

Expand Down

0 comments on commit 9fee592

Please sign in to comment.