module ASF::LDAP

Constants

CONNECT_LOCK

Mutex preventing simultaneous connections to LDAP from a single process

HOSTS

Derived from the following sources:

Updated 2017-08-01

HOST_QUEUE

Round robin list of LDAP hosts to be tried after failure

Public Class Methods

add(dn, list) click to toggle source

add an entry to LDAP; dump information on LDAP errors

# File lib/whimsy/asf/ldap.rb, line 268
def self.add(dn, list)
  ASF.ldap.add(dn, list)
rescue ::LDAP::ResultError
  Wunderbar.warn(list.inspect)
  Wunderbar.warn(dn.to_s)
  raise
end
bind(user=nil, password=nil, &block) click to toggle source

connect to LDAP with a user and password; generally required for update operations. If a block is passed, the connection will be closed after the block executes.

when run interactively, will default user and prompt for password

# File lib/whimsy/asf/ldap.rb, line 128
def self.bind(user=nil, password=nil, &block)
  if not user or not password
    raise ArgumentError.new('wrong number of arguments') unless STDIN.isatty

    require 'etc'
    require 'io/console'
    user ||= Etc.getlogin
    password = STDIN.getpass("Password for #{user}:")
  end

  dn = ASF::Person.new(user).dn
  raise ::LDAP::ResultError.new('Unknown user') unless dn

  ASF.ldap.unbind if ASF.ldap.bound? rescue nil
  ldap = ASF.init_ldap(true)
  if block
    ASF.flush_weakrefs
    ldap.bind(dn, password, &block)
    ASF.init_ldap(true)
  else
    ldap.bind(dn, password)
  end
ensure
  ASF.flush_weakrefs
end
configure() click to toggle source

update /etc/ldap.conf. Usage:

sudo ruby -r whimsy/asf -e "ASF::LDAP.configure"
# File lib/whimsy/asf/ldap.rb, line 218
def self.configure
  cert = Dir["#{ETCLDAP}/asf*-ldap-client.pem"].first

  # verify/obtain/write the cert
  if not cert
    cert = "#{ETCLDAP}/asf-ldap-client.pem"
    File.write cert, ASF::LDAP.puppet_cert || self.extract_cert
  end

  # read the current configuration file
  ldap_conf = "#{ETCLDAP}/ldap.conf"
  content = File.read(ldap_conf)

  # ensure that the right cert is used
  unless content =~ /asf.*-ldap-client\.pem/
    content.gsub!(/^TLS_CACERT/i, '# TLS_CACERT')
    content += "TLS_CACERT #{ETCLDAP}/asf-ldap-client.pem\n"
  end

  # provide the URIs of the ldap hosts
  content.gsub!(/^URI/, '# URI')
  content += "uri \n" unless content =~ /^uri /
  content[/uri (.*)\n/, 1] = hosts.join(' ')

  # verify/set the base
  unless content.include? 'base dc=apache'
    content.gsub!(/^BASE/i, '# BASE')
    content += "base dc=apache,dc=org\n"
  end

  # ensure TLS_REQCERT is allow (Mac OS/X only)
  if ETCLDAP.include? 'openldap' and not content.include? 'REQCERT allow'
    content.gsub!(/^TLS_REQCERT/i, '# TLS_REQCERT')
    content += "TLS_REQCERT allow\n"
  end

  # write the configuration if there were any changes
  File.write(ldap_conf, content) unless content == File.read(ldap_conf)
end
connect(test = true) click to toggle source

connect to LDAP

# File lib/whimsy/asf/ldap.rb, line 87
def self.connect(test = true)
  # Try each host at most once
  hosts.length.times do
    # Ensure we use each host in turn
    hosts.each {|host| HOST_QUEUE.push host} if HOST_QUEUE.empty?
    host = HOST_QUEUE.shift

    Wunderbar.info "[#{host}] - Connecting to LDAP server"

    begin
      # request connection
      uri = URI.parse(host)
      if uri.scheme == 'ldaps'
        ldap = ::LDAP::SSLConn.new(uri.host, uri.port)
      else
        ldap = ::LDAP::Conn.new(uri.host, uri.port)
      end

      # test the connection
      ldap.bind if test

      # save the host
      @host = host

      return ldap
    rescue ::LDAP::ResultError => re
      Wunderbar.warn "[#{host}] - Error connecting to LDAP server: " +
        re.message + " (continuing)"
    end

  end

  Wunderbar.error "Failed to connect to any LDAP host"
  return nil
end
delete(dn) click to toggle source

delete an entry from LDAP; dump information on LDAP errors

# File lib/whimsy/asf/ldap.rb, line 277
def self.delete(dn)
  ASF.ldap.delete(dn)
rescue ::LDAP::ResultError
  Wunderbar.warn(dn.to_s)
  raise
end
extract_cert() click to toggle source

query and extract cert from openssl output

# File lib/whimsy/asf/ldap.rb, line 206
def self.extract_cert
  host = hosts.sample[%r{//(.*?)(/|$)}, 1]
  puts ['openssl', 's_client', '-connect', host, '-showcerts'].join(' ')
  out, err, rc = Open3.capture3 'openssl', 's_client',
    '-connect', host, '-showcerts'
  out[/^-+BEGIN.*?\n-+END[^\n]+\n/m]
end
host() click to toggle source

Return the last chosen host (if any)

# File lib/whimsy/asf/ldap.rb, line 174
def self.host
  @host
end
hosts() click to toggle source

determine what LDAP hosts are available

# File lib/whimsy/asf/ldap.rb, line 179
def self.hosts
  return @hosts if @hosts # cache the hosts list
  # try whimsy config
  hosts = Array(ASF::Config.get(:ldap))

  # check system configuration
  if hosts.empty?
    conf = "#{ETCLDAP}/ldap.conf"
    if File.exist? conf
      uris = File.read(conf)[/^uri\s+(.*)/i, 1].to_s
      hosts = uris.scan(/ldaps?:\/\/\S+?:\d+/)
      Wunderbar.debug "Using hosts from LDAP config"
    end
  else
    Wunderbar.debug "Using hosts from Whimsy config"
  end

  # if all else fails, use default list
  Wunderbar.debug "Using default host list" if hosts.empty?
  hosts = ASF::LDAP::HOSTS if hosts.empty?

  hosts.shuffle!
  #Wunderbar.debug "Hosts:\n#{hosts.join(' ')}"
  @hosts = hosts
end
http_auth(string, &block) click to toggle source

validate HTTP authorization, and optionally invoke a block bound to that user.

# File lib/whimsy/asf/ldap.rb, line 156
def self.http_auth(string, &block)
  auth = Base64.decode64(string.to_s[/Basic (.*)/, 1] || '')
  user, password = auth.split(':', 2)
  return unless password

  if block
    self.bind(user, password, &block)
  else
    begin
      ASF::LDAP.bind(user, password) {}
      return ASF::Person.new(user)
    rescue ::LDAP::ResultError
      return nil
    end
  end
end
modify(dn, list) click to toggle source

modify an entry in LDAP; dump information on LDAP errors

# File lib/whimsy/asf/ldap.rb, line 259
def self.modify(dn, list)
  ASF.ldap.modify(dn, list)
rescue ::LDAP::ResultError
  Wunderbar.warn(list.inspect)
  Wunderbar.warn(dn.to_s)
  raise
end
puppet_cert() click to toggle source

extract the ldapcert from the puppet configuration

# File lib/whimsy/asf/ldap.rb, line 74
def self.puppet_cert
  puppet_config['ldapclient::ldapcert']
end
puppet_config() click to toggle source

fetch configuration from apache/infrastructure-puppet

# File lib/whimsy/asf/ldap.rb, line 65
def self.puppet_config
  return @puppet if @puppet
  # the enclosing method is optional, so we only require the gem here
  require 'yaml'
  require_relative 'git' # just in case
  @puppet = YAML.load(ASF::Git.infra_puppet('data/common.yaml'))
end
puppet_ldapservers() click to toggle source

extract the ldap servers from the puppet configuration

# File lib/whimsy/asf/ldap.rb, line 79
def self.puppet_ldapservers
  puppet_config['ldapserver::slapd_peers'].values.
    map {|host| "ldaps://#{host}:636"}
rescue
  []
end