summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/ldap
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-05-12 17:00:48 -0500
committerLuke Kanies <luke@madstop.com>2008-05-12 17:00:48 -0500
commit17e8158e35336291c551da03067b55dd815ab539 (patch)
tree7783b1f3d08ea9eeea7116d522018acabf438f10 /lib/puppet/util/ldap
parentc56e9a6a0a9491270e22363e750046f284ee2793 (diff)
downloadpuppet-17e8158e35336291c551da03067b55dd815ab539.tar.gz
puppet-17e8158e35336291c551da03067b55dd815ab539.tar.xz
puppet-17e8158e35336291c551da03067b55dd815ab539.zip
Adding ldap providers for the user and group type.
These providers use posixAccount and posixGroup. This is a collapsed merge, fwiw.
Diffstat (limited to 'lib/puppet/util/ldap')
-rw-r--r--lib/puppet/util/ldap/connection.rb57
-rw-r--r--lib/puppet/util/ldap/generator.rb45
-rw-r--r--lib/puppet/util/ldap/manager.rb281
3 files changed, 383 insertions, 0 deletions
diff --git a/lib/puppet/util/ldap/connection.rb b/lib/puppet/util/ldap/connection.rb
new file mode 100644
index 000000000..abcc07ecb
--- /dev/null
+++ b/lib/puppet/util/ldap/connection.rb
@@ -0,0 +1,57 @@
+#
+# Created by Luke Kanies on 2008-3-23.
+# Copyright (c) 2008. All rights reserved.
+require 'puppet/util/ldap'
+
+class Puppet::Util::Ldap::Connection
+ attr_accessor :host, :port, :user, :password, :reset, :ssl
+
+ attr_reader :connection
+
+ def close
+ connection.unbind if connection.bound?
+ end
+
+ def initialize(host, port, options = {})
+ raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" unless Puppet.features.ldap?
+
+ @host, @port = host, port
+
+ options.each do |param, value|
+ begin
+ send(param.to_s + "=", value)
+ rescue
+ raise ArgumentError, "LDAP connections do not support %s parameters" % param
+ end
+ end
+ end
+
+ # Create a per-connection unique name.
+ def name
+ [host, port, user, password, ssl].collect { |p| p.to_s }.join("/")
+ end
+
+ # Should we reset the connection?
+ def reset?
+ reset
+ end
+
+ # Start our ldap connection.
+ def start
+ begin
+ case ssl
+ when :tls:
+ @connection = LDAP::SSLConn.new(host, port, true)
+ when true:
+ @connection = LDAP::SSLConn.new(host, port)
+ else
+ @connection = LDAP::Conn.new(host, port)
+ end
+ @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
+ @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON)
+ @connection.simple_bind(user, password)
+ rescue => detail
+ raise Puppet::Error, "Could not connect to LDAP: %s" % detail
+ end
+ end
+end
diff --git a/lib/puppet/util/ldap/generator.rb b/lib/puppet/util/ldap/generator.rb
new file mode 100644
index 000000000..2a868b0d9
--- /dev/null
+++ b/lib/puppet/util/ldap/generator.rb
@@ -0,0 +1,45 @@
+#
+# Created by Luke Kanies on 2008-3-28.
+# Copyright (c) 2008. All rights reserved.
+require 'puppet/util/ldap'
+
+class Puppet::Util::Ldap::Generator
+ # Declare the attribute we'll use to generate the value.
+ def from(source)
+ @source = source
+ return self
+ end
+
+ # Actually do the generation.
+ def generate(value = nil)
+ if value.nil?
+ @generator.call
+ else
+ @generator.call(value)
+ end
+ end
+
+ # Initialize our generator with the name of the parameter
+ # being generated.
+ def initialize(name)
+ @name = name
+ end
+
+ def name
+ @name.to_s
+ end
+
+ def source
+ if defined?(@source) and @source
+ @source.to_s
+ else
+ nil
+ end
+ end
+
+ # Provide the code that does the generation.
+ def with(&block)
+ @generator = block
+ return self
+ end
+end
diff --git a/lib/puppet/util/ldap/manager.rb b/lib/puppet/util/ldap/manager.rb
new file mode 100644
index 000000000..9761fc753
--- /dev/null
+++ b/lib/puppet/util/ldap/manager.rb
@@ -0,0 +1,281 @@
+require 'puppet/util/ldap'
+require 'puppet/util/ldap/connection'
+require 'puppet/util/ldap/generator'
+
+# The configuration class for LDAP providers, plus
+# connection handling for actually interacting with ldap.
+class Puppet::Util::Ldap::Manager
+ attr_reader :objectclasses, :puppet2ldap, :location, :rdn
+
+ # A null-op that just returns the config.
+ def and
+ return self
+ end
+
+ # Set the offset from the search base and return the config.
+ def at(location)
+ @location = location
+ return self
+ end
+
+ # The basic search base.
+ def base
+ [location, Puppet[:ldapbase]].join(",")
+ end
+
+ # Convert the name to a dn, then pass the args along to
+ # our connection.
+ def create(name, attributes)
+ attributes = attributes.dup
+
+ # Add the objectclasses
+ attributes["objectClass"] = objectclasses.collect { |o| o.to_s }
+ attributes["objectClass"] << "top" unless attributes["objectClass"].include?("top")
+
+ attributes[rdn.to_s] = [name]
+
+ # Generate any new values we might need.
+ generate(attributes)
+
+ # And create our resource.
+ connect { |conn| conn.add dn(name), attributes }
+ end
+
+ # Open, yield, and close the connection. Cannot be left
+ # open, at this point.
+ def connect
+ raise ArgumentError, "You must pass a block to #connect" unless block_given?
+
+ unless defined?(@connection) and @connection
+ if Puppet[:ldaptls]
+ ssl = :tls
+ elsif Puppet[:ldapssl]
+ ssl = true
+ else
+ ssl = false
+ end
+ options = {:ssl => ssl}
+ if user = Puppet[:ldapuser] and user != ""
+ options[:user] = user
+ end
+ if password = Puppet[:ldappassword] and password != ""
+ options[:password] = password
+ end
+ @connection = Puppet::Util::Ldap::Connection.new(Puppet[:ldapserver], Puppet[:ldapport], options)
+ end
+ @connection.start
+ begin
+ yield @connection.connection
+ ensure
+ @connection.close
+ end
+ return nil
+ end
+
+ # Convert the name to a dn, then pass the args along to
+ # our connection.
+ def delete(name)
+ connect { |connection| connection.delete dn(name) }
+ end
+
+ # Calculate the dn for a given resource.
+ def dn(name)
+ ["#{rdn.to_s}=%s" % name, base].join(",")
+ end
+
+ # Convert an ldap-style entry hash to a provider-style hash.
+ def entry2provider(entry)
+ raise ArgumentError, "Could not get dn from ldap entry" unless entry["dn"]
+
+ # DN is always a single-entry array. Strip off the bits before the
+ # first comma, then the bits after the remaining equal sign. This is the
+ # name.
+ name = entry["dn"].dup.pop.split(",").shift.split("=").pop
+
+ result = {:name => name}
+
+ @ldap2puppet.each do |ldap, puppet|
+ result[puppet] = entry[ldap.to_s] || :absent
+ end
+
+ result
+ end
+
+ # Create our normal search filter.
+ def filter
+ return "objectclass=%s" % objectclasses[0] if objectclasses.length == 1
+ return "(&(objectclass=" + objectclasses.join(")(objectclass=") + "))"
+ end
+
+ # Find the associated entry for a resource. Returns a hash, minus
+ # 'dn', or nil if the entry cannot be found.
+ def find(name)
+ result = nil
+ connect do |conn|
+ begin
+ conn.search2(dn(name), 0, "objectclass=*") do |result|
+ # Convert to puppet-appropriate attributes
+ return entry2provider(result)
+ end
+ rescue => detail
+ return nil
+ end
+ end
+ end
+
+ # Declare a new attribute generator.
+ def generates(parameter)
+ @generators << Puppet::Util::Ldap::Generator.new(parameter)
+ @generators[-1]
+ end
+
+ # Generate any extra values we need to make the ldap entry work.
+ def generate(values)
+ return unless @generators.length > 0
+
+ @generators.each do |generator|
+ # Don't override any values that might exist.
+ next if values[generator.name]
+
+ if generator.source
+ unless value = values[generator.source]
+ raise ArgumentError, "%s must be defined to generate %s" % [generator.source, generator.name]
+ end
+ result = generator.generate(value)
+ else
+ result = generator.generate
+ end
+
+ result = [result] unless result.is_a?(Array)
+ result = result.collect { |r| r.to_s }
+
+ values[generator.name] = result
+ end
+ end
+
+ def initialize
+ @rdn = :cn
+ @generators = []
+ end
+
+ # Specify what classes this provider models.
+ def manages(*classes)
+ @objectclasses = classes
+ return self
+ end
+
+ # Specify the attribute map. Assumes the keys are the puppet
+ # attributes, and the values are the ldap attributes, and creates a map
+ # for each direction.
+ def maps(attributes)
+ # The map with the puppet attributes as the keys
+ @puppet2ldap = attributes
+
+ # and the ldap attributes as the keys.
+ @ldap2puppet = attributes.inject({}) { |map, ary| map[ary[1]] = ary[0]; map }
+
+ return self
+ end
+
+ # Return the ldap name for a puppet attribute.
+ def ldap_name(attribute)
+ @puppet2ldap[attribute].to_s
+ end
+
+ # Convert the name to a dn, then pass the args along to
+ # our connection.
+ def modify(name, mods)
+ connect { |connection| connection.modify dn(name), mods }
+ end
+
+ # Specify the rdn that we use to build up our dn.
+ def named_by(attribute)
+ @rdn = attribute
+ self
+ end
+
+ # Return the puppet name for an ldap attribute.
+ def puppet_name(attribute)
+ @ldap2puppet[attribute]
+ end
+
+ # Search for all entries at our base. A potentially expensive search.
+ def search(sfilter = nil)
+ sfilter ||= filter()
+
+ result = []
+ connect do |conn|
+ conn.search2(base, 1, sfilter) do |entry|
+ result << entry2provider(entry)
+ end
+ end
+ return nil if result.empty?
+ return result
+ end
+
+ # Update the ldap entry with the desired state.
+ def update(name, is, should)
+ if should[:ensure] == :absent
+ Puppet.info "Removing %s from ldap" % dn(name)
+ delete(name)
+ return
+ end
+
+ # We're creating a new entry
+ if is.empty? or is[:ensure] == :absent
+ Puppet.info "Creating %s in ldap" % dn(name)
+ # Remove any :absent params and :ensure, then convert the names to ldap names.
+ attrs = ldap_convert(should)
+ create(name, attrs)
+ return
+ end
+
+ # We're modifying an existing entry. Yuck.
+
+ mods = []
+ # For each attribute we're deleting that is present, create a
+ # modify instance for deletion.
+ [is.keys, should.keys].flatten.uniq.each do |property|
+ # They're equal, so do nothing.
+ next if is[property] == should[property]
+
+ attributes = ldap_convert(should)
+
+ prop_name = ldap_name(property).to_s
+
+ # We're creating it.
+ if is[property] == :absent or is[property].nil?
+ mods << LDAP::Mod.new(LDAP::LDAP_MOD_ADD, prop_name, attributes[prop_name])
+ next
+ end
+
+ # We're deleting it
+ if should[property] == :absent or should[property].nil?
+ mods << LDAP::Mod.new(LDAP::LDAP_MOD_DELETE, prop_name, [])
+ next
+ end
+
+ # We're replacing an existing value
+ mods << LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE, prop_name, attributes[prop_name])
+ end
+
+ modify(name, mods)
+ end
+
+ # Is this a complete ldap configuration?
+ def valid?
+ location and objectclasses and ! objectclasses.empty? and puppet2ldap
+ end
+
+ private
+
+ # Convert a hash of attributes to ldap-like forms. This mostly means
+ # getting rid of :ensure and making sure everything's an array of strings.
+ def ldap_convert(attributes)
+ attributes.reject { |param, value| value == :absent or param == :ensure }.inject({}) do |result, ary|
+ value = (ary[1].is_a?(Array) ? ary[1] : [ary[1]]).collect { |v| v.to_s }
+ result[ldap_name(ary[0])] = value
+ result
+ end
+ end
+end