diff options
author | Luke Kanies <luke@madstop.com> | 2008-05-12 17:00:48 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-05-12 17:00:48 -0500 |
commit | 17e8158e35336291c551da03067b55dd815ab539 (patch) | |
tree | 7783b1f3d08ea9eeea7116d522018acabf438f10 /lib/puppet/util/ldap | |
parent | c56e9a6a0a9491270e22363e750046f284ee2793 (diff) | |
download | puppet-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.rb | 57 | ||||
-rw-r--r-- | lib/puppet/util/ldap/generator.rb | 45 | ||||
-rw-r--r-- | lib/puppet/util/ldap/manager.rb | 281 |
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 |