From f21f15e259f84e9fb1968ad9d2650cd7820c29da Mon Sep 17 00:00:00 2001 From: fe80 Date: Mon, 29 Nov 2021 10:52:31 +0100 Subject: [PATCH 1/5] add scram-sha-256 support --- .../postgresql/postgresql_password.rb | 53 +++++++++++++++++-- manifests/server/role.pp | 17 ++++-- spec/spec_helper_local.rb | 18 ++++++- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/lib/puppet/functions/postgresql/postgresql_password.rb b/lib/puppet/functions/postgresql/postgresql_password.rb index f22db18f05..b5659e6f3e 100644 --- a/lib/puppet/functions/postgresql/postgresql_password.rb +++ b/lib/puppet/functions/postgresql/postgresql_password.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require 'openssl' +require 'base64' # @summary This function returns the postgresql password hash from the clear text username / password Puppet::Functions.create_function(:'postgresql::postgresql_password') do @@ -8,23 +10,66 @@ # The clear text `password` # @param sensitive # If the Postgresql-Passwordhash should be of Datatype Sensitive[String] + # @param hash + # Set type for password hash + # @param salt + # Use a specific salt value for scram-sha-256, default is username # # @return # The postgresql password hash from the clear text username / password. dispatch :default_impl do required_param 'Variant[String[1], Integer]', :username required_param 'Variant[String[1], Sensitive[String[1]], Integer]', :password + required_param "Enum['md5', 'scram-sha-256']", :hash optional_param 'Boolean', :sensitive + optional_param 'Optional[Variant[String[1], Integer]]', :salt return_type 'Variant[String, Sensitive[String]]' end - def default_impl(username, password, sensitive = false) + def default_impl(username, password, hash, sensitive = false, salt = nil) password = password.unwrap if password.respond_to?(:unwrap) - result_string = 'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s) + pass = if hash == 'md5' + 'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s) + else + pg_sha256(password, (salt || username)) + end if sensitive - Puppet::Pops::Types::PSensitiveType::Sensitive.new(result_string) + Puppet::Pops::Types::PSensitiveType::Sensitive.new(pass) else - result_string + pass end end + + def pg_sha256(password, salt) + digest = digest_key(password, salt) + 'SCRAM-SHA-256$%s:%s$%s:%s' % [ + '4096', + Base64.strict_encode64(salt), + Base64.strict_encode64(client_key(digest)), + Base64.strict_encode64(server_key(digest)) + ] + end + + def digest_key(password, salt) + OpenSSL::KDF.pbkdf2_hmac( + password, + salt: salt, + iterations: 4096, + length: 32, + hash: OpenSSL::Digest::SHA256.new + ) + end + + def client_key(digest_key) + hmac = OpenSSL::HMAC.new(digest_key, OpenSSL::Digest::SHA256.new) + hmac << 'Client Key' + hmac.digest + OpenSSL::Digest.new('SHA256').digest hmac.digest + end + + def server_key(digest_key) + hmac = OpenSSL::HMAC.new(digest_key, OpenSSL::Digest::SHA256.new) + hmac << 'Server Key' + hmac.digest + end end diff --git a/manifests/server/role.pp b/manifests/server/role.pp index cba36ec54c..4103431fff 100644 --- a/manifests/server/role.pp +++ b/manifests/server/role.pp @@ -18,6 +18,8 @@ # @param psql_group Sets the OS group to run psql # @param psql_path Sets path to psql command # @param module_workdir Specifies working directory under which the psql command should be executed. May need to specify if '/tmp' is on volume mounted with noexec option. +# @param hash Specify the hash method for pg password +# @param salt Specify the salt use for the scram-sha-256 encoding password (default username) define postgresql::server::role ( $update_password = true, Variant[Boolean, String, Sensitive[String]] $password_hash = false, @@ -37,6 +39,8 @@ $psql_path = $postgresql::server::psql_path, $module_workdir = $postgresql::server::module_workdir, Enum['present', 'absent'] $ensure = 'present', + Enum['md5', 'scram-sha-256'] $hash = 'md5', + Optional[Variant[String[1], Integer]] $salt = undef, ) { $password_hash_unsensitive = if $password_hash =~ Sensitive[String] { $password_hash.unwrap @@ -130,14 +134,19 @@ } if $password_hash_unsensitive and $update_password { - if($password_hash_unsensitive =~ /^md5.+/) { + if($password_hash_unsensitive =~ /^(md5|SCRAM-SHA-256).+/) { $pwd_hash_sql = $password_hash_unsensitive } else { - $pwd_md5 = md5("${password_hash_unsensitive}${username}") - $pwd_hash_sql = "md5${pwd_md5}" + $pwd_hash_sql = postgresql::postgresql_password( + $username, + $password_hash, + $hash, + $password_hash =~ Sensitive[String], + $salt, + ) } postgresql_psql { "ALTER ROLE ${username} ENCRYPTED PASSWORD ****": - command => Sensitive("ALTER ROLE \"${username}\" ${password_sql}"), + command => Sensitive("ALTER ROLE \"${username}\" ENCRYPTED PASSWORD '${pwd_hash_sql}'"), unless => Sensitive("SELECT 1 FROM pg_shadow WHERE usename = '${username}' AND passwd = '${pwd_hash_sql}'"), sensitive => true, } diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index 0137edd85b..f9429f9d68 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -48,10 +48,24 @@ def param(type, title, param) it { is_expected.not_to eq(nil) } it { - is_expected.to run.with_params('foo', 'bar').and_return('md596948aad3fcae80c08a35c9b5958cd89') + is_expected.to run.with_params('foo', 'bar', 'md5').and_return( + 'md596948aad3fcae80c08a35c9b5958cd89' + ) } it { - is_expected.to run.with_params('foo', 1234).and_return('md539a0e1b308278a8de5e007cd1f795920') + is_expected.to run.with_params('foo', 1234, 'md5').and_return( + 'md539a0e1b308278a8de5e007cd1f795920' + ) + } + it { + is_expected.to run.with_params('foo', 'bar', 'scram-sha-256').and_return( + 'SCRAM-SHA-256$4096:YmFy$y1VOaTvvs4V3OECvMzre9FtgCZClGuBLVE6sNPsTKbs=:HwFqmSKbihSyHMqkhufOy++cWCFIoTRSg8y6YgeALzE=' + ) + } + it { + is_expected.to run.with_params('foo', 'bar', 'scram-sha-256', nil, 'salt').and_return( + 'SCRAM-SHA-256$4096:c2FsdA==$zOt2zFfUQMbpQf3/vRnYB33QDK/L7APOBHniLy39j/4=:DcW5Jp8Do7wYhVp1f9aT0cyhUfzIAozGcvzXZj+M3YI=' + ) } it 'raises an error if there is only 1 argument' do is_expected.to run.with_params('foo').and_raise_error(StandardError) From aefc062623eddced3232cde79de60e2579160b6e Mon Sep 17 00:00:00 2001 From: fe80 Date: Thu, 16 Dec 2021 11:11:00 +0100 Subject: [PATCH 2/5] change md5 per default --- lib/puppet/functions/postgresql/postgresql_password.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/functions/postgresql/postgresql_password.rb b/lib/puppet/functions/postgresql/postgresql_password.rb index b5659e6f3e..75fb26efbf 100644 --- a/lib/puppet/functions/postgresql/postgresql_password.rb +++ b/lib/puppet/functions/postgresql/postgresql_password.rb @@ -26,7 +26,7 @@ return_type 'Variant[String, Sensitive[String]]' end - def default_impl(username, password, hash, sensitive = false, salt = nil) + def default_impl(username, password, hash = 'md5', sensitive = false, salt = nil) password = password.unwrap if password.respond_to?(:unwrap) pass = if hash == 'md5' 'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s) From 79432cdc315b66142772cc0b71232249e7500961 Mon Sep 17 00:00:00 2001 From: fe80 Date: Thu, 16 Dec 2021 11:16:51 +0100 Subject: [PATCH 3/5] fix order --- lib/puppet/functions/postgresql/postgresql_password.rb | 2 +- manifests/server/role.pp | 2 +- spec/spec_helper_local.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/puppet/functions/postgresql/postgresql_password.rb b/lib/puppet/functions/postgresql/postgresql_password.rb index 75fb26efbf..a70fb2b8ba 100644 --- a/lib/puppet/functions/postgresql/postgresql_password.rb +++ b/lib/puppet/functions/postgresql/postgresql_password.rb @@ -26,7 +26,7 @@ return_type 'Variant[String, Sensitive[String]]' end - def default_impl(username, password, hash = 'md5', sensitive = false, salt = nil) + def default_impl(username, password, sensitive = false, hash = 'md5', salt = nil) password = password.unwrap if password.respond_to?(:unwrap) pass = if hash == 'md5' 'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s) diff --git a/manifests/server/role.pp b/manifests/server/role.pp index 4103431fff..00edc75bd5 100644 --- a/manifests/server/role.pp +++ b/manifests/server/role.pp @@ -140,8 +140,8 @@ $pwd_hash_sql = postgresql::postgresql_password( $username, $password_hash, - $hash, $password_hash =~ Sensitive[String], + $hash, $salt, ) } diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index f9429f9d68..5d9b349a80 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -48,22 +48,22 @@ def param(type, title, param) it { is_expected.not_to eq(nil) } it { - is_expected.to run.with_params('foo', 'bar', 'md5').and_return( + is_expected.to run.with_params('foo', 'bar').and_return( 'md596948aad3fcae80c08a35c9b5958cd89' ) } it { - is_expected.to run.with_params('foo', 1234, 'md5').and_return( + is_expected.to run.with_params('foo', 1234).and_return( 'md539a0e1b308278a8de5e007cd1f795920' ) } it { - is_expected.to run.with_params('foo', 'bar', 'scram-sha-256').and_return( + is_expected.to run.with_params('foo', 'bar', nil, 'scram-sha-256').and_return( 'SCRAM-SHA-256$4096:YmFy$y1VOaTvvs4V3OECvMzre9FtgCZClGuBLVE6sNPsTKbs=:HwFqmSKbihSyHMqkhufOy++cWCFIoTRSg8y6YgeALzE=' ) } it { - is_expected.to run.with_params('foo', 'bar', 'scram-sha-256', nil, 'salt').and_return( + is_expected.to run.with_params('foo', 'bar', nil, 'scram-sha-256', 'salt').and_return( 'SCRAM-SHA-256$4096:c2FsdA==$zOt2zFfUQMbpQf3/vRnYB33QDK/L7APOBHniLy39j/4=:DcW5Jp8Do7wYhVp1f9aT0cyhUfzIAozGcvzXZj+M3YI=' ) } From d192f7693dfef97dbbc5b8fac2065cdce6bb42f6 Mon Sep 17 00:00:00 2001 From: fe80 Date: Thu, 16 Dec 2021 11:35:03 +0100 Subject: [PATCH 4/5] fix optional --- lib/puppet/functions/postgresql/postgresql_password.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/functions/postgresql/postgresql_password.rb b/lib/puppet/functions/postgresql/postgresql_password.rb index a70fb2b8ba..688b36fecc 100644 --- a/lib/puppet/functions/postgresql/postgresql_password.rb +++ b/lib/puppet/functions/postgresql/postgresql_password.rb @@ -20,8 +20,8 @@ dispatch :default_impl do required_param 'Variant[String[1], Integer]', :username required_param 'Variant[String[1], Sensitive[String[1]], Integer]', :password - required_param "Enum['md5', 'scram-sha-256']", :hash optional_param 'Boolean', :sensitive + optional_param "Enum['md5', 'scram-sha-256']", :hash optional_param 'Optional[Variant[String[1], Integer]]', :salt return_type 'Variant[String, Sensitive[String]]' end From c657ae094f32965af917ba0f616c33ef60c8fdd3 Mon Sep 17 00:00:00 2001 From: fe80 Date: Mon, 20 Dec 2021 10:23:00 +0100 Subject: [PATCH 5/5] fix optional --- lib/puppet/functions/postgresql/postgresql_password.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/functions/postgresql/postgresql_password.rb b/lib/puppet/functions/postgresql/postgresql_password.rb index 688b36fecc..89de4b9d9e 100644 --- a/lib/puppet/functions/postgresql/postgresql_password.rb +++ b/lib/puppet/functions/postgresql/postgresql_password.rb @@ -21,7 +21,7 @@ required_param 'Variant[String[1], Integer]', :username required_param 'Variant[String[1], Sensitive[String[1]], Integer]', :password optional_param 'Boolean', :sensitive - optional_param "Enum['md5', 'scram-sha-256']", :hash + optional_param "Optional[Enum['md5', 'scram-sha-256']]", :hash optional_param 'Optional[Variant[String[1], Integer]]', :salt return_type 'Variant[String, Sensitive[String]]' end