class OpenShift::Runtime::ApplicationContainerExt::Kerberos::K5login

Public: manage kerberos principals allowed access to an account

This class will use the system /etc/krb5.conf file and the

libdefaults

k5login_directory value to determine where to place

the k5login file. If the krb5.conf file does not exist or the k5login_directory is not defined, then the file will be placed in $HOME/.k5login for each account.

SEE ALSO

man krb5.conf
man k5login

Examples

k = K5login.new(username)

k.add_principal('user1@EXAMPLE.COM', id=nil)
k.del_principal('user2@EXAMPLE.COM', id=nil)

allowed = k.principals

k.principals = { 'user3@EXAMPLE.COM' => [<idlist], 
                 'user4@EXAMPLE.COM' => [<idlist]
               }

Attributes

config_file[R]
container[R]
filename[R]
group[RW]
lockfile[RW]
mode[RW]
owner[RW]
username[R]

Public Class Methods

new(container, config_file=nil, filename=nil) click to toggle source

Public: Create a new K5login manager

username - [String] the username to manage #config_file - [String] optional kerberos configuration file filename - [String] optional k5login file to manage

Examples

Normal use: 
  k = K5login.new(username)

Testing with a k5login_directory from a test configuration
  testk = K5login.new('testuser', '/tmp/krb5.conf')

Testing with an explicit k5login filename
  testk = K5login.new('testuser2', nil, '/tmp/test_k5login')
# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 115
def initialize(container, config_file=nil, filename=nil)
  @container = container
  @username = container.uuid
  @config_file = config_file ? File.expand_path(config_file) : '/etc/krb5.conf'
  @filename = filename ? File.expand_path(filename) : k5login_file

  # override for testing
  user = Etc.getpwnam('root')
  @owner = user.uid # root
  begin
    @group = Etc.getpwnam(@username).gid
  rescue ArgumentError => e
    @group = @@default_group
  end
  @mode = @@default_mode

  @lockfile = "/var/lock/oo-k5login.#{@username}"
end

Private Class Methods

clone(phash) click to toggle source

return a deep copy of a principal hash

# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 227
def self.clone(phash)
  nhash = phash.clone
  nhash.each_key {|k| nhash[k] = nhash[k].clone }
end
compare(h0, h1) click to toggle source

deep compare of two principal hashes

check that the array of keys is the same and then that the contents of all sets is identical

# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 237
def self.compare(h0, h1)
  h0.keys === h1.keys && h0.map {|k, v| h1[k] === v}.reduce(:&)
end

Public Instance Methods

add_principal(principal, id=nil) click to toggle source

Public: add a single principal to the current set

principal: String - a Kerberos principal key_name: String - an identifier to manage duplicate principals

from different users
currently discarded pending additional work
# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 174
def add_principal(principal, id=nil)
  modify do |_principals|
    if _principals.member? principal
      # add an id to the existing principal (dup safe for sets)
      _principals[principal] << id if not id == nil
    else 
      # add the principal # id set is empty if id is nil
      _principals.merge!({principal => Set.new(id ? [id] : [])})
    end
  end
end
k5login_file(config_file=nil) click to toggle source

Public: Determine the location of the user's k5login file

It resides in the user's home directory unless otherwise specified in the system /etc/krb5.conf

Returns: String - The absolute path to the k5login file

# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 141
def k5login_file(config_file=nil)
  
  config_file ||= @config_file
  if File.exists? config_file

    krb5_config = ParseConfig.new config_file
    if (libdefaults = krb5_config['libdefaults']) and (k5login_directory = libdefaults['k5login_directory'])
      return k5login_directory + "/" + @username
    end

  end

  @container.container_dir + "/.k5login"
end
principals() click to toggle source

Public: retrieve and return the current contents of the k5login file

@return [Array] the list of allowed Kerberos principals

Examples

k = K5login.new(username)
p = k.principals
# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 164
def principals
  modify
end
remove_principal(principal, id=nil) click to toggle source

Public: delete a single principal from the current set

principal: String - a Kerberos principal key_name: String - an identifier to manage duplicate principals

from different users
currently discarded pending additional work
# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 192
def remove_principal(principal, id=nil)
  modify do |_principals|
    _principals.select! do |p, idset|
      # keep non-match or matches with non-empty id sets
      p != principal or idset.delete(id).length > 0
    end
  end
end
replace_principals(new_principals) click to toggle source

Public: replace all principals

new_principals: Array of Hashes:

{'key' => String,  a kerberos principal
 'type' => String == 'krb5-principal',
 'comment' => String - an identifier to manage duplicate
                       principals from different users
                       currently discarded pending more work
# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 209
def replace_principals(new_principals)
  modify do | _principals |
    # remove all entries
    _principals.delete_if {|p| true }
    
    # add all of the new entries
    new_principals.each do |p|
      _principals[p['key']] = Set.new if not _principals[p['key']]
      _principals[p['key']] << p['comment'] if p['comment'] and p['comment'].strip.length > 0
    end
  end

end

Private Instance Methods

modify() { |_principals| ... } click to toggle source

Private: retrieve and update the contents of the k5login file

@yields [Array] the current list of allowed user principals @return [Array] the final list of allowed user principals

This method performs both synchronization (via a mutex) and file locking to avoid race conditions and collisions. It requires the priviledges necessary to read and write files owned by arbitrary users as well as to establish system wide shared locks.

# File lib/openshift-origin-node/model/application_container_ext/kerberos.rb, line 251
def modify

  _principals = {}
  @@mutex.synchronize do

    # prevent external race conditions/collisions
    lockfile_flags = File::RDWR|File::CREAT|File::TRUNC
    lockfile_mode = 0600

    File.open(@lockfile, lockfile_flags, lockfile_mode) do | lock |
      lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
      lock.flock(File::LOCK_EX)
      
      begin
        flags =  File::RDWR|File::CREAT
        mode = 0640

        changed = false
        File.open(@filename, flags, mode) do | file |

          id_set = Set.new

          #
          # Create the list of principals and a set of ids for each
          #
          file.readlines.each do |line|
            line.strip!

            # skip empty lines
            next if line.length == 0

            # build up principal "ids" who have placed this principal
            m = line.match /#\s*id\s*:\s*(.*)$/
            if m
              id_set << (m[1] == '' ? nil : m[1])
              next
            end

            # principals are non-comment non-empty lines
            id_set << nil if not line.start_with?('#') and
              id_set.length == 0

            # add the principal to the hash
            _principals[line] = id_set
            # and reset for the next iteration
            id_set = Set.new
  
          end

          if block_given?
            old_principals = K5login.clone(_principals)

            yield _principals
            
            if not K5login.compare(old_principals, _principals)
              file.seek(0, IO::SEEK_SET)
              # write all of the ids and then the principal string
              _principals.each {|principal, id_list|
                id_list.each {|id|
                  file.write "# id: #{id}\n"
                }
                file.write principal + "\n\n"
              }
              # remove the extra newline
              file.truncate(file.tell - 1) if file.tell > 0
              changed = true
            end
          end
        end
        if changed
          # set ownership
          File.chown @owner, @group, @filename 

          # set permissions
          File.chmod @mode, @filename if @mode

          # set SELinux labels
          cmd = "restorecon  #{@filename}"
          ::OpenShift::Runtime::Utils::oo_spawn(cmd)
        end
      ensure
        lock.flock(File::LOCK_UN)
        lock.close
        File.delete lockfile
      end
    end
  end
  
  @principals = _principals
  File.delete @filename if @principals.length == 0
  @principals
end