(MODULES-1737) Add pw_hash() function
This commit is contained in:
parent
c297bd80e3
commit
23be4020dd
4 changed files with 195 additions and 0 deletions
|
@ -500,6 +500,24 @@ Calling the class or definition from outside the current module will fail. For e
|
|||
|
||||
*Type*: statement
|
||||
|
||||
#### `pw_hash`
|
||||
|
||||
Hashes a password using the crypt function. Provides a hash usable on most POSIX systems.
|
||||
|
||||
The first argument to this function is the password to hash. If it is undef or an empty string, this function returns undef.
|
||||
|
||||
The second argument to this function is which type of hash to use. It will be converted into the appropriate crypt(3) hash specifier. Valid hash types are:
|
||||
|
||||
|Hash type |Specifier|
|
||||
|---------------------|---------|
|
||||
|MD5 |1 |
|
||||
|SHA-256 |5 |
|
||||
|SHA-512 (recommended)|6 |
|
||||
|
||||
The third argument to this function is the salt to use.
|
||||
|
||||
Note: this uses the Puppet Master's implementation of crypt(3). If your environment contains several different operating systems, ensure that they are compatible before using this function.
|
||||
|
||||
#### `range`
|
||||
|
||||
When given range in the form of '(start, stop)', `range` extrapolates a range as an array. For example, `range("0", "9")` returns [0,1,2,3,4,5,6,7,8,9]. Zero-padded strings are converted to integers automatically, so `range("00", "09")` returns [0,1,2,3,4,5,6,7,8,9].
|
||||
|
|
56
lib/puppet/parser/functions/pw_hash.rb
Normal file
56
lib/puppet/parser/functions/pw_hash.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
Puppet::Parser::Functions::newfunction(
|
||||
:pw_hash,
|
||||
:type => :rvalue,
|
||||
:arity => 3,
|
||||
:doc => "Hashes a password using the crypt function. Provides a hash
|
||||
usable on most POSIX systems.
|
||||
|
||||
The first argument to this function is the password to hash. If it is
|
||||
undef or an empty string, this function returns undef.
|
||||
|
||||
The second argument to this function is which type of hash to use. It
|
||||
will be converted into the appropriate crypt(3) hash specifier. Valid
|
||||
hash types are:
|
||||
|
||||
|Hash type |Specifier|
|
||||
|---------------------|---------|
|
||||
|MD5 |1 |
|
||||
|SHA-256 |5 |
|
||||
|SHA-512 (recommended)|6 |
|
||||
|
||||
The third argument to this function is the salt to use.
|
||||
|
||||
Note: this uses the Puppet Master's implementation of crypt(3). If your
|
||||
environment contains several different operating systems, ensure that they
|
||||
are compatible before using this function.") do |args|
|
||||
raise ArgumentError, "pw_hash(): wrong number of arguments (#{args.size} for 3)" if args.size != 3
|
||||
raise ArgumentError, "pw_hash(): first argument must be a string" unless args[0].is_a? String or args[0].nil?
|
||||
raise ArgumentError, "pw_hash(): second argument must be a string" unless args[1].is_a? String
|
||||
hashes = { 'md5' => '1',
|
||||
'sha-256' => '5',
|
||||
'sha-512' => '6' }
|
||||
hash_type = hashes[args[1].downcase]
|
||||
raise ArgumentError, "pw_hash(): #{args[1]} is not a valid hash type" if hash_type.nil?
|
||||
raise ArgumentError, "pw_hash(): third argument must be a string" unless args[2].is_a? String
|
||||
raise ArgumentError, "pw_hash(): third argument must not be empty" if args[2].empty?
|
||||
raise ArgumentError, "pw_hash(): characters in salt must be in the set [a-zA-Z0-9./]" unless args[2].match(/\A[a-zA-Z0-9.\/]+\z/)
|
||||
|
||||
password = args[0]
|
||||
return nil if password.nil? or password.empty?
|
||||
|
||||
# handle weak implementations of String#crypt
|
||||
if 'test'.crypt('$1$1') != '$1$1$Bp8CU9Oujr9SSEw53WV6G.'
|
||||
# JRuby < 1.7.17
|
||||
if RUBY_PLATFORM == 'java'
|
||||
# override String#crypt for password variable
|
||||
def password.crypt(salt)
|
||||
# puppetserver bundles Apache Commons Codec
|
||||
org.apache.commons.codec.digest.Crypt.crypt(self.to_java_bytes, salt)
|
||||
end
|
||||
else
|
||||
# MS Windows and other systems that don't support enhanced salts
|
||||
raise Puppet::ParseError, 'system does not support enhanced salts'
|
||||
end
|
||||
end
|
||||
password.crypt("$#{hash_type}$#{args[2]}")
|
||||
end
|
34
spec/acceptance/pw_hash_spec.rb
Normal file
34
spec/acceptance/pw_hash_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
#! /usr/bin/env ruby -S rspec
|
||||
require 'spec_helper_acceptance'
|
||||
|
||||
# Windows and OS X do not have useful implementations of crypt(3)
|
||||
describe 'pw_hash function', :unless => (UNSUPPORTED_PLATFORMS + ['windows', 'Darwin']).include?(fact('operatingsystem')) do
|
||||
describe 'success' do
|
||||
it 'hashes passwords' do
|
||||
pp = <<-EOS
|
||||
$o = pw_hash('password', 6, 'salt')
|
||||
notice(inline_template('pw_hash is <%= @o.inspect %>'))
|
||||
EOS
|
||||
|
||||
apply_manifest(pp, :catch_failures => true) do |r|
|
||||
expect(r.stdout).to match(/pw_hash is "\$6\$salt\$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy\.g\."/)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil if no password is provided' do
|
||||
pp = <<-EOS
|
||||
$o = pw_hash('', 6, 'salt')
|
||||
notice(inline_template('pw_hash is <%= @o.inspect %>'))
|
||||
EOS
|
||||
|
||||
apply_manifest(pp, :catch_failures => true) do |r|
|
||||
expect(r.stdout).to match(/pw_hash is ""/)
|
||||
end
|
||||
end
|
||||
end
|
||||
describe 'failure' do
|
||||
it 'handles less than three arguments'
|
||||
it 'handles more than three arguments'
|
||||
it 'handles non strings'
|
||||
end
|
||||
end
|
87
spec/functions/pw_hash_spec.rb
Normal file
87
spec/functions/pw_hash_spec.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
#! /usr/bin/env ruby -S rspec
|
||||
require 'spec_helper'
|
||||
|
||||
describe "the pw_hash function" do
|
||||
let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
|
||||
|
||||
it "should exist" do
|
||||
expect(Puppet::Parser::Functions.function("pw_hash")).to eq("function_pw_hash")
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if there are less than 3 arguments" do
|
||||
expect { scope.function_pw_hash([]) }.to( raise_error(ArgumentError, /[Ww]rong number of arguments/) )
|
||||
expect { scope.function_pw_hash(['password']) }.to( raise_error(ArgumentError, /[Ww]rong number of arguments/) )
|
||||
expect { scope.function_pw_hash(['password', 'sha-512']) }.to( raise_error(ArgumentError, /[Ww]rong number of arguments/) )
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if there are more than 3 arguments" do
|
||||
expect { scope.function_pw_hash(['password', 'sha-512', 'salt', 5]) }.to( raise_error(ArgumentError, /[Ww]rong number of arguments/) )
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the first argument is not a string" do
|
||||
expect { scope.function_pw_hash([['password'], 'sha-512', 'salt']) }.to( raise_error(ArgumentError, /first argument must be a string/) )
|
||||
# in Puppet 3, numbers are passed as strings, so we can't test that
|
||||
end
|
||||
|
||||
it "should return nil if the first argument is empty" do
|
||||
expect(scope.function_pw_hash(['', 'sha-512', 'salt'])).to eq(nil)
|
||||
end
|
||||
|
||||
it "should return nil if the first argument is undef" do
|
||||
expect(scope.function_pw_hash([nil, 'sha-512', 'salt'])).to eq(nil)
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the second argument is an invalid hash type" do
|
||||
expect { scope.function_pw_hash(['', 'invalid', 'salt']) }.to( raise_error(ArgumentError, /not a valid hash type/) )
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the second argument is not a string" do
|
||||
expect { scope.function_pw_hash(['', [], 'salt']) }.to( raise_error(ArgumentError, /second argument must be a string/) )
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the third argument is not a string" do
|
||||
expect { scope.function_pw_hash(['password', 'sha-512', ['salt']]) }.to( raise_error(ArgumentError, /third argument must be a string/) )
|
||||
# in Puppet 3, numbers are passed as strings, so we can't test that
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the third argument is empty" do
|
||||
expect { scope.function_pw_hash(['password', 'sha-512', '']) }.to( raise_error(ArgumentError, /third argument must not be empty/) )
|
||||
end
|
||||
|
||||
it "should raise an ArgumentError if the third argument has invalid characters" do
|
||||
expect { scope.function_pw_hash(['password', 'sha-512', '%']) }.to( raise_error(ArgumentError, /characters in salt must be in the set/) )
|
||||
end
|
||||
|
||||
it "should fail on platforms with weak implementations of String#crypt" do
|
||||
String.any_instance.expects(:crypt).with('$1$1').returns('$1SoNol0Ye6Xk')
|
||||
expect { scope.function_pw_hash(['password', 'sha-512', 'salt']) }.to( raise_error(Puppet::ParseError, /system does not support enhanced salts/) )
|
||||
end
|
||||
|
||||
it "should return a hashed password" do
|
||||
result = scope.function_pw_hash(['password', 'sha-512', 'salt'])
|
||||
expect(result).to eql('$6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g.')
|
||||
end
|
||||
|
||||
it "should use the specified salt" do
|
||||
result = scope.function_pw_hash(['password', 'sha-512', 'salt'])
|
||||
expect(result).to match('salt')
|
||||
end
|
||||
|
||||
it "should use the specified hash type" do
|
||||
resultmd5 = scope.function_pw_hash(['password', 'md5', 'salt'])
|
||||
resultsha256 = scope.function_pw_hash(['password', 'sha-256', 'salt'])
|
||||
resultsha512 = scope.function_pw_hash(['password', 'sha-512', 'salt'])
|
||||
|
||||
expect(resultmd5).to eql('$1$salt$qJH7.N4xYta3aEG/dfqo/0')
|
||||
expect(resultsha256).to eql('$5$salt$Gcm6FsVtF/Qa77ZKD.iwsJlCVPY0XSMgLJL0Hnww/c1')
|
||||
expect(resultsha512).to eql('$6$salt$IxDD3jeSOb5eB1CX5LBsqZFVkJdido3OUILO5Ifz5iwMuTS4XMS130MTSuDDl3aCI6WouIL9AjRbLCelDCy.g.')
|
||||
end
|
||||
|
||||
it "should generate a valid hash" do
|
||||
password_hash = scope.function_pw_hash(['password', 'sha-512', 'salt'])
|
||||
|
||||
hash_parts = password_hash.match(%r{\A\$(.*)\$([a-zA-Z0-9./]+)\$([a-zA-Z0-9./]+)\z})
|
||||
|
||||
expect(hash_parts).not_to eql(nil)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue