Skip to content

Commit f47c32e

Browse files
committed
add scram-sha-256 support
1 parent dd190cd commit f47c32e

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed
Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# frozen_string_literal: true
2+
require 'openssl'
3+
require 'base64'
24

35
# @summary This function returns the postgresql password hash from the clear text username / password
46
Puppet::Functions.create_function(:'postgresql::postgresql_password') do
@@ -8,23 +10,66 @@
810
# The clear text `password`
911
# @param sensitive
1012
# If the Postgresql-Passwordhash should be of Datatype Sensitive[String]
13+
# @param encode
14+
# Set the encode type for password hash
15+
# @param salt
16+
# Use a specific salt value for scram-sha-256, default is username
1117
#
1218
# @return
1319
# The postgresql password hash from the clear text username / password.
1420
dispatch :default_impl do
1521
required_param 'Variant[String[1], Integer]', :username
1622
required_param 'Variant[String[1], Sensitive[String[1]], Integer]', :password
23+
required_param "Enum['md5', 'scram-sha-256']", :encode
1724
optional_param 'Boolean', :sensitive
25+
optional_param 'Optional[Variant[String[1], Integer]]', :salt
1826
return_type 'Variant[String, Sensitive[String]]'
1927
end
2028

21-
def default_impl(username, password, sensitive = false)
29+
def default_impl(username, password, encode, sensitive = false, salt = nil)
2230
password = password.unwrap if password.respond_to?(:unwrap)
23-
result_string = 'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s)
31+
pass = if encode == 'md5'
32+
'md5' + Digest::MD5.hexdigest(password.to_s + username.to_s)
33+
else
34+
pg_sha256(password, (salt || username))
35+
end
2436
if sensitive
25-
Puppet::Pops::Types::PSensitiveType::Sensitive.new(result_string)
37+
Puppet::Pops::Types::PSensitiveType::Sensitive.new(pass)
2638
else
27-
result_string
39+
pass
2840
end
2941
end
42+
43+
def pg_sha256(password, salt)
44+
digest = digest_key(password, salt)
45+
'SCRAM-SHA-256$%s:%s$%s:%s' % [
46+
'4096',
47+
Base64.strict_encode64(salt),
48+
Base64.strict_encode64(client_key(digest)),
49+
Base64.strict_encode64(server_key(digest))
50+
]
51+
end
52+
53+
def digest_key(password, salt)
54+
OpenSSL::KDF.pbkdf2_hmac(
55+
password,
56+
salt: salt,
57+
iterations: 4096,
58+
length: 32,
59+
hash: OpenSSL::Digest::SHA256.new
60+
)
61+
end
62+
63+
def client_key(digest_key)
64+
hmac = OpenSSL::HMAC.new(digest_key, OpenSSL::Digest::SHA256.new)
65+
hmac << 'Client Key'
66+
hmac.digest
67+
OpenSSL::Digest.new('SHA256').digest hmac.digest
68+
end
69+
70+
def server_key(digest_key)
71+
hmac = OpenSSL::HMAC.new(digest_key, OpenSSL::Digest::SHA256.new)
72+
hmac << 'Server Key'
73+
hmac.digest
74+
end
3075
end

manifests/server/role.pp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
# @param psql_group Sets the OS group to run psql
1919
# @param psql_path Sets path to psql command
2020
# @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.
21+
# @param encode Specify the encoding method for pg password
22+
# @param salt Specify the salt use for the scram-sha-256 encoding password (default username)
2123
define postgresql::server::role (
2224
$update_password = true,
2325
Variant[Boolean, String, Sensitive[String]] $password_hash = false,
@@ -37,6 +39,8 @@
3739
$psql_path = $postgresql::server::psql_path,
3840
$module_workdir = $postgresql::server::module_workdir,
3941
Enum['present', 'absent'] $ensure = 'present',
42+
Enum['md5', 'scram-sha-256'] $encode = 'md5',
43+
Optional[Variant[String[1], Integer]] $salt = undef,
4044
) {
4145
$password_hash_unsensitive = if $password_hash =~ Sensitive[String] {
4246
$password_hash.unwrap
@@ -130,14 +134,19 @@
130134
}
131135

132136
if $password_hash_unsensitive and $update_password {
133-
if($password_hash_unsensitive =~ /^md5.+/) {
137+
if($password_hash_unsensitive =~ /^(md5|SCRAM-SHA-256).+/) {
134138
$pwd_hash_sql = $password_hash_unsensitive
135139
} else {
136-
$pwd_md5 = md5("${password_hash_unsensitive}${username}")
137-
$pwd_hash_sql = "md5${pwd_md5}"
140+
$pwd_hash_sql = postgresql::postgresql_password(
141+
$username,
142+
$password_hash,
143+
$encode,
144+
$password_hash =~ Sensitive[String],
145+
$salt,
146+
)
138147
}
139148
postgresql_psql { "ALTER ROLE ${username} ENCRYPTED PASSWORD ****":
140-
command => Sensitive("ALTER ROLE \"${username}\" ${password_sql}"),
149+
command => Sensitive("ALTER ROLE \"${username}\" ENCRYPTED PASSWORD '${pwd_hash_sql}'"),
141150
unless => Sensitive("SELECT 1 FROM pg_shadow WHERE usename = '${username}' AND passwd = '${pwd_hash_sql}'"),
142151
sensitive => true,
143152
}

spec/spec_helper_local.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,24 @@ def param(type, title, param)
4848
it { is_expected.not_to eq(nil) }
4949

5050
it {
51-
is_expected.to run.with_params('foo', 'bar').and_return('md596948aad3fcae80c08a35c9b5958cd89')
51+
is_expected.to run.with_params('foo', 'bar', 'md5').and_return(
52+
'md596948aad3fcae80c08a35c9b5958cd89'
53+
)
5254
}
5355
it {
54-
is_expected.to run.with_params('foo', 1234).and_return('md539a0e1b308278a8de5e007cd1f795920')
56+
is_expected.to run.with_params('foo', 1234, 'md5').and_return(
57+
'md539a0e1b308278a8de5e007cd1f795920'
58+
)
59+
}
60+
it {
61+
is_expected.to run.with_params('foo', 'bar', 'scram-sha-256').and_return(
62+
'SCRAM-SHA-256$4096:YmFy$y1VOaTvvs4V3OECvMzre9FtgCZClGuBLVE6sNPsTKbs=:HwFqmSKbihSyHMqkhufOy++cWCFIoTRSg8y6YgeALzE='
63+
)
64+
}
65+
it {
66+
is_expected.to run.with_params('foo', 'bar', 'scram-sha-256', nil, 'salt').and_return(
67+
'SCRAM-SHA-256$4096:c2FsdA==$zOt2zFfUQMbpQf3/vRnYB33QDK/L7APOBHniLy39j/4=:DcW5Jp8Do7wYhVp1f9aT0cyhUfzIAozGcvzXZj+M3YI='
68+
)
5569
}
5670
it 'raises an error if there is only 1 argument' do
5771
is_expected.to run.with_params('foo').and_raise_error(StandardError)

0 commit comments

Comments
 (0)