Class: Sshakery::AuthKeys

Inherits:
Object
  • Object
show all
Defined in:
lib/sshakery/auth_keys.rb

Overview

for reading from and writing to an authorized_keys file.

Constant Summary collapse

STATE_ATTRIBUTES =

Attributes that help define the current state of the key

[ 
    :errors,
    :raw_line,
    :saved
]
KEY_ATTRIBUTES =

:category: Instance Attributes Attributes that are read from / written to authorized_keys files. They are listed in the order that they should appear in the authorized_keys file

command

(string) -> A forced shell command to run

key.command = 'ls'
permitopen

(string) -> TODO: document

tunnel

(integer) -> Port forwarding

key.tunnel = 5950
from

(string) -> IP/host address required for client

environment

(array) -> Array of strings to set shell environment variables Not tested

key.environment.push 'RAILS_ENV=production'
no_agent_forwarding
(boolean) -> Don’t allow ssh agent authentication forwarding
no_port_forwarding

(boolean) -> Don’t allow port forwarding

no_pty

(boolean) -> Don’t create terminal for client

no_user_rc

(boolean) -> Don’t process user rc files

no_X11_forwarding

(boolean) -> Don’t allow X11 forwarding. Please note the uppercase ‘X’ in X11

key_type

(string) -> Type of key. ‘ssh-dsa’ or ‘ssh-rsa’

key_data

(string) -> A Base64 string of public key data

note

(string) -> A note about a key. No spaces allowed

[
    :command,
    :permitopen,
    :tunnel,
    :from,
    :environment,
    :no_agent_forwarding,
    :no_port_forwarding,
    :no_pty,
    :no_user_rc,
    :no_X11_forwarding,
    :key_type,
    :key_data,
    :note
]
ATTRIBUTES =

A list of all attributes a key has

STATE_ATTRIBUTES+KEY_ATTRIBUTES
DEFAULTS =

Attribute default values

{
    :errors => []
}.freeze
BOOL_ATTRIBUTES =

Boolean attributes

[
    :no_agent_forwarding,
    :no_port_forwarding,
    :no_pty,
    :no_user_rc,
    :no_X11_forwarding
]
STR_ATTRIBUTES =

STR_ATTRIBUTES is a list of attributes that are strings

  • gen_raw_line will return a line containing the contents of these variables (if any)

[
    :key_type,
    :key_data,
    :note
]
ARR_STR_ATTRIBUTES =

each is equal to a joined string of their values

  • gen_raw_line will return a line containing the contents

of these variables (if any)

[
    :environment
]
SUB_STR_ATTRIBUTES =

add string and substitute attr value

  • gen_raw_line will return a line containing the contents of these variables (if any)

{
    :command=>'command="%sub%"',
    :permitopen=>'permitopen="%sub%"',
    :tunnel=>'tunnel="%sub%"',
    :from=>'from="%sub%"'
}
TYPE_REGEX =

A regex for matching ssh key types imported from a pub key file

/ssh-dss|ssh-rsa/
B64_REGEX =

A regex for matching base 64 strings

/[A-Za-z0-9\/\+]+={0,3}/
OPTS_REGEX =

The regex used for reading individual lines/records in an authorized_keys file

{
    :key_type=> /(#{TYPE_REGEX}) (?:#{B64_REGEX})/,
    :key_data=> /(?:#{TYPE_REGEX}) (#{B64_REGEX})/,
    :note=>/([A-Za-z0-9_\/\+@]+)\s*$/,
    :command=>/command="([^"]+)"(?: |,)/,
    :environment=>/([A-Z0-9]+=[^\s]+)(?: |,)/,
    :from=>/from="([^"])"(?: |,)/,
    :no_agent_forwarding=>/(no-agent-forwarding)(?: |,)/,
    :no_port_forwarding=>/(no-port-forwarding)(?: |,)/,
    :no_pty=>/(no-pty)(?: |,)/,
    :no_user_rc=>/(no-user-rc)(?: |,)/,
    :no_X11_forwarding=>/(no-X11-forwarding)(?: |,)/,
    :permitopen=>/permitopen="([a-z0-9.]+:[\d]+)"(?: |,)/,
    :tunnel=>/tunnel="(\d+)"(?: |,)/
}
ERRORS =

This is a list of attribute errors

{
    :data_modulus=> {:key_data=>'public key length is not a modulus of 4'}, 
    :data_short  => {:key_data=>'public key is too short'},
    :data_long   => {:key_data=>'public key is too long'},
    :data_char   => {:key_data=>'public key contains invalid base64 characters'},
    :data_nil    => {:key_data=>'public key is missing'},
    :type_nil    => {:key_type=>'missing key type'},
    :bool        => 'bad value for boolean field'
}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ AuthKeys

Create a new key object



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/sshakery/auth_keys.rb', line 254

def initialize(args={}) 
    ATTRIBUTES.each do |attr|
        instance_variable_set("@#{attr}", args.has_key?( attr ) ? args[attr] : nil )
    end

    self.raw_line = args[:raw_pubkey] || args[:raw_line]

    if args.has_key? :raw_pubkey
        self.load_pubkey
        return
    end

    unless self.raw_line.nil? 
        self.load_raw_line
    end
end

Class Attribute Details

.pathObject

Path to authorized_keys file



169
170
171
# File 'lib/sshakery/auth_keys.rb', line 169

def path
  @path
end

.temp_pathObject

Path to lock file (cannot be the same as key file)



172
173
174
# File 'lib/sshakery/auth_keys.rb', line 172

def temp_path
  @temp_path
end

Class Method Details

.allObject

Return array of all keys



244
245
246
247
248
249
250
# File 'lib/sshakery/auth_keys.rb', line 244

def self.all
   result = []
   File.readlines(self.path).each do |line|
        result.push( self.new(:raw_line => line ))
   end
   return result
end

.destroy(auth_key) ⇒ Object

Delete a key



215
216
217
# File 'lib/sshakery/auth_keys.rb', line 215

def self.destroy(auth_key)
    self.write auth_key, destroy=true
end

.find_all_by(fields = {}, with_regex = false) ⇒ Object

Search the authorized_keys file for keys containing a field with a specific value

Args :

  • fields -> A hash of key value pairs to match against

  • with_regex -> Use regex matching (default=false)

Returns :

  • Array -> An array of keys that matched

Usage :

keys = Sshakery.load '/home/someuser/.ssh/authorized_keys'
foo_keys = keys.find_all_by :note=>'foo'
fc_keys = keys.find_all_by :command=>'ls', :no_X11_forwarding=>true
rsa_keys = keys.find_all_by :key_data=>'ssh-rsa'


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/sshakery/auth_keys.rb', line 191

def self.find_all_by(fields ={}, with_regex=false)
    result = []
    
    self.all.each do |auth_key|
        
        all_matched = true

        fields.each do |field,value|
            if with_regex &&  auth_key.send(field).to_s.match(value.to_s)
                next
            elsif auth_key.send(field) == value
                next
            end
            all_matched = false
        end
        
        result.push auth_key if all_matched

    end
    return result
end

.write(auth_key, destroy = false) ⇒ Object

Create, update or delete the contents of a key



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/sshakery/auth_keys.rb', line 221

def self.write(auth_key, destroy=false)
    lines = []
    FsUtils.atomic_lock(:path=>self.path) do |f|
        f.each_line do |line|
            key=self.new(:raw_line => line )

            if key.key_data == auth_key.key_data 
                lines.push auth_key.gen_raw_line if destroy==false
            else
                lines.push line 
            end
        end
        f.rewind
        f.truncate(0)
        lines.each do |line|
            f.puts line
        end
    end
    return true
end

Instance Method Details

#all_no=(val) ⇒ Object

Set all boolean attributes at the same time

  • val -> (boolean)



326
327
328
329
330
# File 'lib/sshakery/auth_keys.rb', line 326

def all_no=(val)
    BOOL_ATTRIBUTES.each do |attr|
        self.instance_variable_set("@#{attr}",val)
    end
end

#attrObject

set attributes



82
83
84
# File 'lib/sshakery/auth_keys.rb', line 82

ATTRIBUTES.each do |attr| #:nodoc:
    attr_accessor attr
end

#destroyObject

Remove a key from the file

Returns :

  • Boolean -> Destroy success status



393
394
395
396
# File 'lib/sshakery/auth_keys.rb', line 393

def destroy
    return false if not self.saved?
    return self.class.destroy self
end

#gen_raw_lineObject

Construct the line that will be written to file

Returns :

  • String -> Line that will be written to file



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/sshakery/auth_keys.rb', line 403

def gen_raw_line
    return nil unless self.valid?
    line = ''
    data = []
    SUB_STR_ATTRIBUTES.each do |field,field_regex|
        val = self.raw_getter field
        data.push val if val.nil? == false
    end
    unless data.empty?
        line = "#{data.join ' ,'}"
    end

    data = []
    BOOL_ATTRIBUTES.each do |field|
        val = self.raw_getter field
        data.push val if val.nil? == false
    end
    unless data.empty?
        if line == ''
            line += "#{data.join ','} "
        else
            line += ",#{data.join ','} "
        end
    end

    data = []
    ARR_STR_ATTRIBUTES.each do |field|
        val = self.raw_getter field
        data.push val if val.nil? == false
    end
    unless data.empty?
        if line == ''
            line += "#{data.join ','} "
        else
            line += ", #{data.join ','} "
        end
    end

    data = []
    STR_ATTRIBUTES.each do |field|
        val = self.raw_getter field
        data.push val if val.nil? == false
    end
    line += data.join ' '
    return line
end

#load_pubkeyObject

Instantiate key based on pubkey file only set key_data,key_type and note attributes



274
275
276
277
278
279
280
# File 'lib/sshakery/auth_keys.rb', line 274

def load_pubkey
    #filter line data
    load_raw_line([:key_data,:key_type,:note]) 
    
    # sanitize old raw line
    self.raw_line = self.gen_raw_line
end

#load_raw_line(opts = OPTS_REGEX.keys) ⇒ Object

Instantiate key object based on contents of raw_line



284
285
286
287
288
289
290
291
# File 'lib/sshakery/auth_keys.rb', line 284

def load_raw_line opts = OPTS_REGEX.keys
    self.raw_line.chomp!
    opts.each do |xfield|
        pattern = OPTS_REGEX[xfield]
        did_set = raw_setter xfield, pattern
        #puts did_set
    end
end

#raw_getter(field) ⇒ Object

Return the string representation of what the attribute will look like in the authorized_keys file

Args :

  • field -> Attribute name

Returns :

  • string -> A string representation of the attribute



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/sshakery/auth_keys.rb', line 340

def raw_getter field
    val = self.instance_variable_get("@#{field}")
    return nil if val.nil? == true ||  val == false

    if BOOL_ATTRIBUTES.include? field
        return field.to_s.gsub '_', '-'   
    end

    if STR_ATTRIBUTES.include? field 
        return val 
    end

    if ARR_STR_ATTRIBUTES.include? field && val.empty? == false 
        return val.join ' '
    end

    if SUB_STR_ATTRIBUTES.include? field 
        return SUB_STR_ATTRIBUTES[field].sub '%sub%', val  
    end 
end

#raw_setter(xfield, pattern) ⇒ Object

set attribute (field) obtained from matching pattern in raw_line



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
# File 'lib/sshakery/auth_keys.rb', line 296

def raw_setter xfield,pattern
   field = "@#{xfield}"
   m = self.raw_line.match pattern
   return false if m.nil?
   #p "#{field} => #{m.inspect}" 
   if BOOL_ATTRIBUTES.include? xfield
       self.instance_variable_set(field, true)
       return true  
   end

   if STR_ATTRIBUTES.include? xfield 
       self.instance_variable_set(field,  m[1])
       return true
   end

   if ARR_STR_ATTRIBUTES.include? xfield 
       self.instance_variable_set(field, m.to_a)
       return true
   end

   if SUB_STR_ATTRIBUTES.include? xfield 
       self.instance_variable_set(field, m[1])
       return true
   end 
   return false
end

#saveObject

Add a key to authorized keys file if it passes validation. If the validations fail the reason for the failure will be found in @errors.

Returns :

  • boolean -> True if save was successful, otherwise returns false



368
369
370
371
# File 'lib/sshakery/auth_keys.rb', line 368

def save
    return false if not self.valid?
    return self.class.write(self)
end

#save!Object

Add a key to authorized keys file if it passes validation, otherwise raise an error if save doesn’t pass validations

Returns :

  • boolean -> True if save was successful, otherwise raises error

Raises :

  • Error -> Sshakery::Errors::RecordInvalid ( Key did not pass validations )



382
383
384
385
386
# File 'lib/sshakery/auth_keys.rb', line 382

def save!
    unless self.save 
        raise Sshakery::Errors::RecordInvalid.new 'Errors preventing save' 
    end
end

#saved?Boolean

Has the key already been saved to file?

Returns :

  • Boolean -> True if has been saved before, otherwise false

Returns:

  • (Boolean)


501
502
503
504
# File 'lib/sshakery/auth_keys.rb', line 501

def saved?
    return false if not self.valid?
    return self.saved           
end

#valid?Boolean

Validate the key If the validations fail the reason for the failure will be found in @errors.

Returns :

  • Boolean -> True if valid, otherwise false

Returns:

  • (Boolean)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/sshakery/auth_keys.rb', line 457

def valid?
    self.errors = []
    
    BOOL_ATTRIBUTES.each do |field|
        val = self.raw_getter field
        unless val.nil? == true || val == true || val == false
            self.errors.push field=>ERRORS[:bool]
        end
    end
    
    if self.key_data.nil?
        self.errors.push ERRORS[:data_nil] 
        return false
    end

    if self.key_type.nil?
        self.errors.push ERRORS[:type_nil] 
        return false
    end

    if not self.key_data.match "^#{B64_REGEX}$"
        self.errors.push ERRORS[:data_char] 
    end

    if self.key_data.size < 30
        self.errors.push ERRORS[:data_short] 
    end

    if self.key_data.size > 1000
        self.errors.push ERRORS[:data_long] 
    end
    
    if self.key_data.size % 4 != 0
        self.errors.push ERRORS[:data_modulus] 
    end

    return self.errors.empty?
end