Puppet Function: simplib::passgen::simpkv::passgen
- Defined in:
- lib/puppet/functions/simplib/passgen/simpkv/passgen.rb
- Function type:
- Ruby 4.x API
Overview
Generates/retrieves a random password string or its hash for a passed identifier.
-
Password info is stored in a key/value store and accessed using simpkv.
-
The minimum length password that this function will return is ‘8` characters.
-
Terminates catalog compilation if ‘password_options` contains invalid parameters, any simpkv operation fails or the password cannot be created in the allotted time.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/puppet/functions/simplib/passgen/simpkv/passgen.rb', line 11 Puppet::Functions.create_function(:'simplib::passgen::simpkv::passgen') do # @param identifier Unique `String` to identify the password usage. # Must conform to the following: # * Identifier must contain only the following characters: # * a-z # * A-Z # * 0-9 # * The following special characters: `._:-/` # * Identifier may not contain '/./' or '/../' sequences. # # @param password_options # Password options # # @option password_options [Boolean] 'last' # Whether to return the last generated password. # Defaults to `false`. # @option password_options [Integer[8]] 'length' # Length of the new password. # Defaults to `32`. # @option password_options [Enum[true,false,'md5',sha256','sha512']] 'hash' # Return a `Hash` of the password instead of the password itself. # Defaults to `false`. `true` is equivalent to 'sha256'. # @option password_options [Integer[0,2]] 'complexity' # Specifies the types of characters to be used in the password # * `0` => Default. Use only Alphanumeric characters in your password (safest) # * `1` => Add reasonably safe symbols # * `2` => Printable ASCII # @option password_options [Boolean] 'complex_only' # Whether to use only the characters explicitly added by the complexity # rules. For example, when `complexity` is `1`, create a password from only # safe symbols. # Defaults to `false`. # @option password_options [Variant[Integer[0],Float[0]]] 'gen_timeout_seconds' # Maximum time allotted to generate the password. # * Value of `0` disables the timeout. # * Defaults to `30`. # # @param simpkv_options simpkv configuration that will be merged with # `simpkv::options`. All keys are optional. # # @option simpkv_options [String] 'app_id' # Specifies an application name that can be used to identify which backend # configuration to use via fuzzy name matching, in the absence of the # `backend` option. # # * More flexible option than `backend`. # * Useful for grouping together simpkv function calls found in different # catalog resources. # * When specified and the `backend` option is absent, the backend will be # selected preferring a backend in the merged `backends` option whose # name exactly matches the `app_id`, followed by the longest backend # name that matches the beginning of the `app_id`, followed by the # `default` backend. # * When absent and the `backend` option is also absent, this function # will use the `default` backend. # # @option simpkv_options [String] 'backend' # Definitive name of the backend to use. # # * Takes precedence over `app_id`. # * When present, must match a key in the `backends` option of the # merged options Hash or the function will fail. # * When absent in the merged options, this function will select # the backend as described in the `app_id` option. # # @option simpkv_options [Hash] 'backends' # Hash of backend configurations # # * Each backend configuration in the merged options Hash must be # a Hash that has the following keys: # # * `type`: Backend type. # * `id`: Unique name for the instance of the backend. (Same backend # type can be configured differently). # # * Other keys for configuration specific to the backend may also be # present. # # @option simpkv_options [String] 'environment' # Puppet environment to prepend to keys. # # * When set to a non-empty string, it is prepended to the key used in # the backend operation. # * Should only be set to an empty string when the key being accessed is # truly global. # * Defaults to the Puppet environment for the node. # # @option simpkv_options [Boolean] 'softfail' # Whether to ignore simpkv operation failures. # # * When `true`, this function will return a result even when the # operation failed at the backend. # * When `false`, this function will fail when the backend operation # failed. # * Defaults to `false`. # # # @return [String] Password or password hash specified. # # * When the `last` password option is `true`, the password is determined # as follows: # # * If the last password exists in the key/value store, uses the existing # last password. # * Otherwise, if the current password exists in the key/value store, # uses the existing current password. # * Otherwise, creates and stores a new password as the current password, # and then uses this new password # # * When `last` option is `false`, the password is determined as follows: # # * If the current password doesn't exist in the key/value store, creates # and stores a new password as the current password, and then uses this # new password. # * Otherwise, if the current password exists in the key/value store and it # has an appropriate length, uses the current password. # * Otherwise, stores the current password as the last password, creates # and stores a new password as the current password, and then uses this # new password. # # @raise Exception if `password_options` contains invalid parameters, # a simpkv operation fails, or password generation times out # dispatch :passgen do required_param 'String[1]', :identifier optional_param 'Hash', :password_options optional_param 'Hash', :simpkv_options end def passgen(identifier, ={}, ={'app_id' => 'simplib::passgen'}) require 'timeout' # internal settings settings = {} settings['min_password_length'] = 8 settings['default_password_length'] = 32 settings['crypt_map'] = { 'md5' => '1', 'sha256' => '5', 'sha512' => '6' } = { 'last' => false, 'length' => settings['default_password_length'], 'hash' => false, 'complexity' => 0, 'complex_only' => false, 'gen_timeout_seconds' => 30 } = (, , settings) password = nil salt = nil begin if ['last'] password,salt = get_last_password(identifier, , ) else password,salt = get_current_password(identifier, , ) end rescue Timeout::Error => e # can get here if password/salt generation timed out fail("simplib::passgen timed out for '#{identifier}'!") end # Return the hash, not the password if ['hash'] return password.crypt("$#{settings['crypt_map'][['hash']]}$#{salt}") else return password end end # Build a merged options hash and validate the options # @raise ArgumentError if any option in the password_options is invalid def (, , settings) = .dup .merge!() # set internal options that help us validate whether a retrieved # password meets current criteria ['length_configured'] = .has_key?('length') ['complexity_configured'] = .has_key?('complexity') ['complex_only_configured'] = .has_key?('complex_only') if ['length'].to_s !~ /^\d+$/ raise ArgumentError, "simplib::passgen: Error: Length '#{['length']}' must be an integer!" else ['length'] = ['length'].to_i if ['length'] == 0 ['length'] = settings['default_password_length'] elsif ['length'] < settings['min_password_length'] ['length'] = settings['min_password_length'] end end if ['complexity'].to_s !~ /^\d+$/ raise ArgumentError, "simplib::passgen: Error: Complexity '#{['complexity']}' must be an integer!" else ['complexity'] = ['complexity'].to_i end if ['gen_timeout_seconds'].to_s !~ /^\d+$/ raise ArgumentError, "simplib::passgen: Error: Password generation timeout '#{['gen_timeout_seconds']}' must be an integer!" else ['gen_timeout_seconds'] = ['gen_timeout_seconds'].to_i end # Make sure a valid hash has been selected if ['hash'] == true ['hash'] = 'sha256' end if ['hash'] and !settings['crypt_map'].keys.include?(['hash']) raise ArgumentError, "simplib::passgen: Error: '#{['hash']}' is not a valid hash." end return end # Create a <password,salt> pair and then store it, pertinent options, and # any <password,salt> history in the key/value store # # @param identifier Password name # @param options Hash of options to use to generate the password # # @return [password, salt] # @raise Timeout::Error if password or salt generation times out # @raise Exception if simpkv operation fails def create_and_store_password(identifier, , ) password, salt = call_function('simplib::passgen::gen_password_and_salt', ['length'], ['complexity'], ['complex_only'], ['gen_timeout_seconds'] ) call_function('simplib::passgen::simpkv::set', identifier, password, salt, ['complexity'], ['complex_only'], ) [password, salt] end # Retrieve or generate a current password and its salt # # * If the current password doesn't exist in the key/value store, generate # both the password and its salt and store them in the key/value store. # * If the current password exists, retrieve it and its salt from the # key/value store, and validate it. # * If the password has the correct length per the options, use it. # * Otherwise, store this password and its salt as the last password in # the key/value store, generate a new the password and salt, and then # store the new values as the current password in the key/value store. # # @return current [password, salt] # @raise if any simpkv operation fails or password/salt generation times out. # def get_current_password(identifier, , ) password = nil salt = nil history = [] generate = false password_info = call_function('simplib::passgen::simpkv::get', identifier, ) if password_info.empty? generate = true else password = password_info['value']['password'] salt = password_info['value']['salt'] generate = true unless valid_password?(password_info, ) end if generate password, salt = create_and_store_password(identifier, , ) end [password, salt] end # Retrieve lastest password and its salt, generating the password # if needed # # * If the password key exists in the key/value and the history is not # empty, use the first entry from that history (i.e., most recent # <password,salt> pair). # * Otherwise, if the password key exists in the key/value and the history # is empty, use the current password and salt # * Otherwise, create a freshly-generated password and salt, store it # in the key/value store and warn the user about a probable manifest # ordering problems. # # @return last [password, salt] # @raise if any simpkv operation fails or password/salt generation times out. # def get_last_password(identifier, , ) password = nil salt = nil password_info = call_function('simplib::passgen::simpkv::get', identifier, ) if password_info.empty? warn_msg = "Could not retrieve a last or current value for" + " #{identifier}. Generating a new value for 'last'. Please ensure" + " that you have used simplib::passgen in the proper order in your" + " manifest!" Puppet.warning warn_msg # generate password and salt and then store password, salt = create_and_store_password(identifier, , ) elsif !password_info['metadata']['history'].empty? password,salt = password_info['metadata']['history'].first else password = password_info['value']['password'] salt = password_info['value']['salt'] end [password, salt] end # @return whether a retrieved password conforms to the current user # specification # @param password_info password info Hash # @param options current options def valid_password?(password_info, ) if ['length_configured'] unless (password_info['value']['password'].length == ['length']) return false end end if ['complexity_configured'] unless ( password_info['metadata'].key?('complexity') && (password_info['metadata']['complexity'] == ['complexity']) ) return false end end if ['complex_only_configured'] unless ( password_info['metadata'].key?('complex_only') && (password_info['metadata']['complex_only'] == ['complex_only']) ) return false end end true end end |