Support for mariadb's ed25519 authentication

Add the ability to configure all mysql users to require authenticating
to the server via mariadb's ed25519 auth plugin [1], rather than the
default native authentication [2].

[1] https://mariadb.com/kb/en/authentication-plugin-ed25519/
[2] https://mariadb.com/kb/en/authentication-plugin-mysql_native_password/

Change-Id: I430ea8e1fa15fb263d1d4ef8c39615021d907f8a
Partial-Bug: #1866093
This commit is contained in:
Damien Ciabrini 2020-03-06 13:17:57 +01:00
parent 7ee97845dd
commit 00a06edc5c
5 changed files with 176 additions and 26 deletions

42
files/mysql_ed25519_password.py Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
import hashlib
import base64
import sys
from nacl.bindings.crypto_scalarmult import \
crypto_scalarmult_ed25519_base_noclamp
# https://github.com/MariaDB/server/blob/10.4/plugin/auth_ed25519/ref10/sign.c
# mariadb's use of ed25519:
# . password is the secret seed
# . ed25519's public key (computed from password) is what is stored in mariadb
# . the hash in mariadb is the base64 encoding of the pk minus the last '='
def _scalar_clamp(s32):
ba = bytearray(s32)
ba0 = bytes(bytearray([ba[0] & 248]))
ba31 = bytes(bytearray([(ba[31] & 127) | 64]))
return ba0 + bytes(s32[1:31]) + ba31
def mysql_ed25519_password(pwd):
# h = SHA512(password)
h = hashlib.sha512(pwd).digest()
# s = prune(first_half(h))
s = _scalar_clamp(h[:32])
# A = encoded point [s]B
A = crypto_scalarmult_ed25519_base_noclamp(s)
# encoded pk
encoded = base64.b64encode(A)[:-1]
return encoded
if __name__ == "__main__":
if len(sys.argv) <= 1:
print("Usage: %s PASSWORD" % sys.argv[0], file=sys.stderr)
sys.exit(1)
else:
pwd = sys.argv[1].encode()
res = mysql_ed25519_password(pwd)
print(res.decode(), end='')

View File

@ -0,0 +1,19 @@
# Custom function to generate password hash for MariaDB's auth_ed25519
# Input is a regular mariadb user password
# Output is the hashed password as expected by auth_ed25519
Puppet::Functions.create_function(:'mysql_ed25519_password') do
dispatch :mysql_ed25519_password do
param 'String', :password
return_type 'String'
end
def mysql_ed25519_password(password)
# mysql's auth_ed25519 consists in generating a ed25519 public key
# out of the sha512(password). Unfortunately, there is no native
# ruby implementation of ed25519's unclamped scalar multiplication
# just yet, so rely on an binary to get the hash for now.
hashed = `/etc/puppet/modules/tripleo/files/mysql_ed25519_password.py #{password}`
raise Puppet::Error, 'generated hash is not 43 bytes long.' unless hashed.length == 43
return hashed
end
end

View File

@ -87,6 +87,11 @@
# (Optional) Maximum number of connections to MySQL. # (Optional) Maximum number of connections to MySQL.
# Defaults to hiera('mysql_max_connections', undef) # Defaults to hiera('mysql_max_connections', undef)
# #
# [*mysql_auth_ed25519*]
# (Optional) Use MariaDB's ed25519 authentication plugin to authenticate
# a user when connecting to the server
# Defaults to hiera('mysql_auth_ed25519', false)
#
# [*remove_default_accounts*] # [*remove_default_accounts*]
# (Optional) Whether or not remove default MySQL accounts. # (Optional) Whether or not remove default MySQL accounts.
# Defaults to true # Defaults to true
@ -112,6 +117,7 @@ class tripleo::profile::base::database::mysql (
$manage_resources = true, $manage_resources = true,
$mysql_server_options = {}, $mysql_server_options = {},
$mysql_max_connections = hiera('mysql_max_connections', undef), $mysql_max_connections = hiera('mysql_max_connections', undef),
$mysql_auth_ed25519 = hiera('mysql_auth_ed25519', false),
$remove_default_accounts = true, $remove_default_accounts = true,
$step = Integer(hiera('step')), $step = Integer(hiera('step')),
) { ) {
@ -174,6 +180,7 @@ class tripleo::profile::base::database::mysql (
'ssl-cert' => $tls_certfile, 'ssl-cert' => $tls_certfile,
'ssl-cipher' => $tls_cipher_list, 'ssl-cipher' => $tls_cipher_list,
'ssl-ca' => undef, 'ssl-ca' => undef,
'plugin_load_add' => 'auth_ed25519',
} }
} }
$mysql_server_options_real = deep_merge($mysql_server_default, $mysql_server_options) $mysql_server_options_real = deep_merge($mysql_server_default, $mysql_server_options)
@ -214,78 +221,88 @@ class tripleo::profile::base::database::mysql (
password_hash => mysql_password(hiera('mysql::server::root_password')), password_hash => mysql_password(hiera('mysql::server::root_password')),
} }
} }
if ($mysql_auth_ed25519) {
['root@localhost', 'root@%'].each |$user| {
Mysql_user<| title == $user |> {
plugin => 'ed25519',
password_hash => mysql_ed25519_password(hiera('mysql::server::root_password'))
}
}
}
# Note: use 'include_and_check_auth' below rather than 'include'
# to support ed25519 authentication
if hiera('aodh_api_enabled', false) { if hiera('aodh_api_enabled', false) {
include aodh::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'aodh::db::mysql':}
} }
if hiera('ceilometer_collector_enabled', false) { if hiera('ceilometer_collector_enabled', false) {
include ceilometer::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'ceilometer::db::mysql':}
} }
if hiera('cinder_api_enabled', false) { if hiera('cinder_api_enabled', false) {
include cinder::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'cinder::db::mysql':}
} }
if hiera('barbican_api_enabled', false) { if hiera('barbican_api_enabled', false) {
include barbican::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'barbican::db::mysql':}
} }
if hiera('designate_api_enabled', false) { if hiera('designate_api_enabled', false) {
include designate::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'designate::db::mysql':}
} }
if hiera('glance_api_enabled', false) { if hiera('glance_api_enabled', false) {
include glance::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'glance::db::mysql':}
} }
if hiera('gnocchi_api_enabled', false) { if hiera('gnocchi_api_enabled', false) {
include gnocchi::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'gnocchi::db::mysql':}
} }
if hiera('heat_engine_enabled', false) { if hiera('heat_engine_enabled', false) {
include heat::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'heat::db::mysql':}
} }
if hiera('ironic_api_enabled', false) { if hiera('ironic_api_enabled', false) {
include ironic::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'ironic::db::mysql':}
} }
if hiera('ironic_inspector_enabled', false) { if hiera('ironic_inspector_enabled', false) {
include ironic::inspector::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'ironic::inspector::db::mysql':}
} }
if hiera('keystone_enabled', false) { if hiera('keystone_enabled', false) {
include keystone::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'keystone::db::mysql':}
} }
if hiera('manila_api_enabled', false) { if hiera('manila_api_enabled', false) {
include manila::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'manila::db::mysql':}
} }
if hiera('mistral_api_enabled', false) { if hiera('mistral_api_enabled', false) {
include mistral::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'mistral::db::mysql':}
} }
if hiera('neutron_api_enabled', false) { if hiera('neutron_api_enabled', false) {
include neutron::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'neutron::db::mysql':}
}
if hiera('nova_conductor_enabled', false) {
tripleo::profile::base::database::mysql::include_and_check_auth{'nova::db::mysql':}
} }
    if hiera('nova_conductor_enabled', false) {
      include nova::db::mysql
    }
if hiera('nova_api_enabled', false) { if hiera('nova_api_enabled', false) {
include nova::db::mysql_api tripleo::profile::base::database::mysql::include_and_check_auth{'nova::db::mysql_api':}
} }
if hiera('placement_enabled', false) { if hiera('placement_enabled', false) {
include placement::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'placement::db::mysql':}
} }
if hiera('octavia_api_enabled', false) { if hiera('octavia_api_enabled', false) {
include octavia::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'octavia::db::mysql':}
} }
if hiera('sahara_api_enabled', false) { if hiera('sahara_api_enabled', false) {
include sahara::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'sahara::db::mysql':}
} }
if hiera('trove_api_enabled', false) { if hiera('trove_api_enabled', false) {
include trove::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'trove::db::mysql':}
} }
if hiera('panko_api_enabled', false) { if hiera('panko_api_enabled', false) {
include panko::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'panko::db::mysql':}
} }
if hiera('ec2_api_enabled', false) { if hiera('ec2_api_enabled', false) {
include ec2api::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'ec2api::db::mysql':}
} }
if hiera('zaqar_api_enabled', false) and hiera('zaqar::db::mysql::user', '') == 'zaqar' { if hiera('zaqar_api_enabled', false) and hiera('zaqar::db::mysql::user', '') == 'zaqar' {
# NOTE: by default zaqar uses sqlalchemy # NOTE: by default zaqar uses sqlalchemy
include zaqar::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'zaqar::db::mysql':}
} }
if hiera('veritas_hyperscale_controller_enabled', false) { if hiera('veritas_hyperscale_controller_enabled', false) {
include veritas_hyperscale::db::mysql tripleo::profile::base::database::mysql::include_and_check_auth{'veritas_hyperscale::db::mysql':}
} }
} }

View File

@ -0,0 +1,49 @@
# Copyright 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# == Class: include_and_check_auth
#
# Include an OpenStack MySQL profile and configures it for alternative
# client authentication like e.g. ed25519
#
# === Parameters
#
# [*module*]
# (Optional) The puppet module to include
# Defaults to $title
#
# [*mysql_auth_ed25519*]
# (Optional) Use MariaDB's ed25519 authentication plugin to authenticate
# a user when connecting to the server
# Defaults to hiera('mysql_auth_ed25519', false)
#
define tripleo::profile::base::database::mysql::include_and_check_auth(
$module = $title,
$mysql_auth_ed25519 = hiera('mysql_auth_ed25519', false),
) {
include $module
if ($mysql_auth_ed25519) {
# currently all openstack puppet modules create MySQL users
# by hashing their password for the default auth method.
# If ed25519 auth is enabled, we must hash the password
# differently; so do it with a collector until all
# openstack modules support ed25519 auth natively.
$stripped_module_name = regsubst($module,'^::','')
$password_key = "${stripped_module_name}::password"
Openstacklib::Db::Mysql<| tag == $stripped_module_name |> {
plugin => 'ed25519',
password_hash => mysql_ed25519_password(hiera($password_key))
}
}
}

View File

@ -105,6 +105,11 @@
# Should be an hash. # Should be an hash.
# Defaults to hiera('tripleo::profile::base::database::mysql::mysql_server_options', {} # Defaults to hiera('tripleo::profile::base::database::mysql::mysql_server_options', {}
# #
# [*mysql_auth_ed25519*]
# (Optional) Use MariaDB's ed25519 authentication plugin to authenticate
# a user when connecting to the server
# Defaults to hiera('mysql_auth_ed25519', false)
#
# [*pcs_tries*] # [*pcs_tries*]
# (Optional) The number of times pcs commands should be retried. # (Optional) The number of times pcs commands should be retried.
# Defaults to hiera('pcs_tries', 20) # Defaults to hiera('pcs_tries', 20)
@ -149,6 +154,7 @@ class tripleo::profile::pacemaker::database::mysql_bundle (
$sst_tls_options = undef, $sst_tls_options = undef,
$ipv6 = str2bool(hiera('mysql_ipv6', false)), $ipv6 = str2bool(hiera('mysql_ipv6', false)),
$mysql_server_options = hiera('tripleo::profile::base::database::mysql::mysql_server_options', {}), $mysql_server_options = hiera('tripleo::profile::base::database::mysql::mysql_server_options', {}),
$mysql_auth_ed25519 = hiera('mysql_auth_ed25519', false),
$container_backend = 'docker', $container_backend = 'docker',
$log_driver = undef, $log_driver = undef,
$tls_priorities = hiera('tripleo::pacemaker::tls_priorities', undef), $tls_priorities = hiera('tripleo::pacemaker::tls_priorities', undef),
@ -498,6 +504,23 @@ MYSQL_HOST=localhost\n",
password_hash => mysql_password($mysql_root_password), password_hash => mysql_password($mysql_root_password),
} }
# declare the clustercheck user resource to configure
# ed25519 authentication on stack creation or update.
if ($mysql_auth_ed25519) {
$clustercheck_resource_config = {
plugin => 'ed25519',
password_hash => mysql_ed25519_password($clustercheck_password),
}
} else {
$clustercheck_resource_config = {
password_hash => mysql_password($clustercheck_password),
}
}
mysql_user { 'clustercheck@localhost':
ensure => present,
* => $clustercheck_resource_config,
}
# We create databases and users for services at step 2 as well. This ensures # We create databases and users for services at step 2 as well. This ensures
# Galera is up and ready before those get created # Galera is up and ready before those get created
File['/root/.my.cnf'] -> Mysql_database<||> File['/root/.my.cnf'] -> Mysql_database<||>