Foreman-proxy : patch to support dhcp-ldap and dns by webservice

Hi,

First of all, thanks for all your work on The Foreman. I'm currently
installing it to manage my servers and I had to modify a bit the
smartproxy so it could :

  • read/write the DHCP config in LDAP
  • call a webservice to add/delete a DNS record

I'm not a good programmer and i don't know ruby very well but if
anyone is interested by these functionalities, here is the code :

John

diff -uNr foreman-proxy/config/settings.yml.example foreman-proxy-
patched/config/settings.yml.example
— foreman-proxy/config/settings.yml.example 2011-06-06
16:52:40.000000000 +0200
+++ foreman-proxy-patched/config/settings.yml.example 2011-08-19
18:14:19.000000000 +0200
@@ -29,6 +29,9 @@

Enable DNS management

:dns: false
+:dns_vendor: ws
+:dns_ws_add: "http://mgt-srv.infra.tld/ws/add_dns.cgi?
fqdn=FQDN&policy=__TYPE___force&ip=IP&ttl=TTL"
+:dns_ws_del: "http://mgt-srv.infra.tld/ws/del_dns.cgi?
fqdn=FQDN&policy=__TYPE___force&ip=IP&ttl=TTL"
#:dns_key: /etc/rndc.key

use this setting if you are managing a dns server which is not

localhost though this proxy
#:dns_server: dns.domain.com
@@ -36,7 +39,11 @@

Enable DHCP management

:dhcp: false

The vendor can be either isc or native_ms

-:dhcp_vendor: isc
+:dhcp_vendor: isc_ldap
+:dhcp_ldapuser: "cn=dhcpadmin,ou=dhcp,dc=infra,dc=tld"
+:dhcp_ldappassword: "password"
+:dhcp_ldaphost: ldap.infra.tld
+:dhcp_ldapbase: "cn=DHCP Config,ou=dhcp,dc=infra,dc=tld"

Settings for Ubuntu ISC

#:dhcp_config: /etc/dhcp3/dhcpd.conf
#:dhcp_leases: /var/lib/dhcp3/dhcpd.leases
diff -uNr foreman-proxy/lib/dhcp_api.rb foreman-proxy-patched/lib/
dhcp_api.rb
— foreman-proxy/lib/dhcp_api.rb 2011-06-06 16:52:40.000000000 +0200
+++ foreman-proxy-patched/lib/dhcp_api.rb 2011-08-18
15:32:41.000000000 +0200
@@ -9,9 +9,16 @@
and File.exist?(SETTINGS.dhcp_config) and File.exist?
(SETTINGS.dhcp_leases)
log_halt 400, "Unable to find the DHCP configuration or lease
files"
end

  •  @server = Proxy::DHCP::ISC.new({:name => "127.0.0.1",
    
  •  @server = Proxy::DHCP::ISC.new({:name => "127.0.1.1",
                                     :config =>
    

File.read(SETTINGS.dhcp_config),
:leases =>
File.read(SETTINGS.dhcp_leases)})

  • when "isc_ldap"
  •  require 'proxy/dhcp/server/isc_ldap'
    
  •  @server = Proxy::DHCP::ISC_LDAP.new({:name =>
    

SETTINGS.dhcp_ldaphost,

  •                                       :ldapuser =>
    

SETTINGS.dhcp_ldapuser,

  •                                       :ldappassword =>
    

SETTINGS.dhcp_ldappassword,

  •                                       :ldaphost =>
    

SETTINGS.dhcp_ldaphost,

  •                                       :ldapbase =>
    

SETTINGS.dhcp_ldapbase})
when "native_ms"
require 'proxy/dhcp/server/native_ms'
@server = Proxy::DHCP::NativeMS.new(:server =>
SETTINGS.dhcp_server ? SETTINGS.dhcp_server : "127.0.0.1")
diff -uNr foreman-proxy/lib/dns_api.rb foreman-proxy-patched/lib/
dns_api.rb
— foreman-proxy/lib/dns_api.rb 2011-06-06 16:52:40.000000000 +0200
+++ foreman-proxy-patched/lib/dns_api.rb 2011-08-19 11:14:25.000000000
+0200
@@ -1,8 +1,14 @@
-require "proxy/dns/bind"

class SmartProxy
def setup(opts)

  • @server = Proxy::DNS::Bind.new(opts.merge(:server =>
    SETTINGS.dns_server))
  • case SETTINGS.dns_vendor.downcase
  • when "bind"
  •    require "proxy/dns/bind"
    
  •    @server = Proxy::DNS::Bind.new(opts.merge(:server =>
    

SETTINGS.dns_server))

  • when "ws"
  •    require "proxy/dns/webservice"
    
  •    @server = Proxy::DNS::Webservice.new(opts.merge({:ws_add =>
    

SETTINGS.dns_ws_add, :ws_del => SETTINGS.dns_ws_del}))

  • end
    end

post "/dns/" do
diff -uNr foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb foreman-
proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb
— foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb 1970-01-01
01:00:00.000000000 +0100
+++ foreman-proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb 2011-08-18
15:35:41.000000000 +0200
@@ -0,0 +1,189 @@
+require 'time'
+require 'net/ldap'

··· + +module Proxy::DHCP + class ISC_LDAP < Server + + def initialize options + super(options[:name]) + @ldapuser = options[:ldapuser] + @ldappassword = options[:ldappassword] + @ldaphost = options[:ldaphost] + @ldapbase = options[:ldapbase] + end + + def delRecord subnet, record + validate_subnet subnet + validate_record record + raise InvalidRecord, "#{record} is static - unable to delete" unless record.deleteable? + + msg = "Removed DHCP reservation for #{record.name} => #{record}" + ldapConnect + filter = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet #{record.mac}") + @ldap.search(:base => @ldapbase, :filter => filter) do |entry| + report "Removed DHCP LDAP record : #{entry.dn}" + @ldap.delete :dn => entry.dn + end + subnet.delete record + end + + def addRecord options = {} + super(options) + ip = options[:ip] + mac = options[:mac] + name = options[:hostname] + subnet = find_subnet(IPAddr.new(ip)) + msg = "Added DHCP reservation for #{name}" + + ldapConnect + dn = "cn=#{name},#{@ldapbase}" + attr = { + :cn => name, + :objectclass => ["top", "dhcpHost"], + :dhcpHWAddress => "ethernet "+mac, + :dhcpStatements => ["fixed-address "+ip] + } + attr[:dhcpStatements].push("filename \"#{options[:filename]} \"") if options[:filename] + attr[:dhcpStatements].push("next-server "+bootServer(options[:nextserver])) if options[:nextserver] + + @ldap.add(:dn => dn, :attributes => attr) + + Proxy::DHCP::Reservation.new(subnet, ip, mac, options) + end + + def loadSubnetData subnet + super + ldapConnect + filter = Net::LDAP::Filter.eq("objectClass","dhcpHost") + @ldap.search(:base => @ldapbase, :filter => filter, :return_result => false) do |entry| + opts = {} + entry.each do |attribute, values| + case attribute.to_s + when "cn" + opts[:title] = values + when "dhcphwaddress" + opts[:mac] = values.to_s.split[1] + when "dhcpstatements" + values.each do |value| + case value.to_s + when /^fixed-address\s+(\S+)/ + opts[:ip] = $1 + when /^filename\s+(\S+)/ + opts[:filename] = $1 + when /^next-server\s+(\S+)/ + opts[:nextServer] = $1 + end + end + end + end + begin + Proxy::DHCP::Reservation.new(subnet, opts[:ip], opts[:mac], opts) if subnet.include? opts[:ip] + rescue Exception => e + logger.warn "skipped #{title} - #{e}" + end + end + + report "Enumerated hosts on #{subnet.network}" + end + + ## performance issue with find_record of Server class + def find_record record + ldapConnect + filter_ip = Net::LDAP::Filter.eq("dhcpStatements","fixed- address #{record}") + filter_mac = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet #{record}") + filter_name = Net::LDAP::Filter.eq("cn","#{record}") + filter = filter_ip | filter_mac | filter_name + @ldap.search(:base => @ldapbase, :filter => filter) do |entry| + return record + end + return nil + end + + private + def ldapConnect + @ldap = Net::LDAP.new :host => @ldaphost, :port => 389, + :auth => { + :method => :simple, + :username => @ldapuser, + :password => @ldappassword + } + end + + def loadSubnets + super + ldapConnect + filter = Net::LDAP::Filter.eq("objectClass","dhcpSubnet") + @ldap.search(:base => @ldapbase, :filter => filter, :return_result => false) do |entry| + opts = {} + entry.each do |attribute, values| + case attribute.to_s + when "cn" + opts[:subnet] = values.to_s + when "dhcpoption" + values.each do |value| + if value.to_s =~ /^subnet-mask\s+([\d\.]+)/ + opts[:subnetmask] = $1 + end + end + end + end + report "subnet new : "+opts[:subnet]+opts[:subnetmask] + Proxy::DHCP::Subnet.new(self, opts[:subnet], opts[:subnetmask]) + end + "Enumerated the scopes on #{@name}" + end + + + def report msg, response="" + if response.nil? or !response.grep(/can't|no more|not connected| Syntax error/).empty? + logger.error "Omshell failed:\n" + (response.nil? ? "No response from DHCP server" : response.join(", ")) + msg.sub! /Removed/, "remove" + msg.sub! /Added/, "add" + msg.sub! /Enumerated/, "enumerate" + msg = "Failed to #{msg}" + msg += ": Entry already exists" if response and response.grep(/object: already exists/).size > 0 + msg += ": No response from DHCP server" if response.nil? or response.grep(/not connected/).size > 0 + raise Proxy::DHCP::Error.new(msg) + else + logger.info msg + end + end + + def ip2hex ip + ip.split(".").map{|i| "%02x" % i }.join(":") + end + + def hex2ip hex + hex.split(":").map{|h| h.to_i(16).to_s}.join(".") + end + + def find_record_by_title subnet, title + subnet.records.each do |v| + return v if v.options[:title] == title + end + end + + # ISC stores timestamps in UTC, therefor forcing the time to load from GMT/UTC TZ + def parse_time str + Time.parse(str +" UTC") + rescue => e + logger.warn "Unable to parse time #{e}" + raise "Unable to parse time #{e}" + end + + def bootServer server + begin + ns = validate_ip(server) + rescue + begin + ns = Resolv.new.getaddress(server) + rescue + logger.warn "Failed to resolve IP address for #{server}" + ns = "\\\"#{server}\\\"" + end + end + "#{ns}" + end + + end +end diff -uNr foreman-proxy/lib/proxy/dns/webservice.rb foreman-proxy- patched/lib/proxy/dns/webservice.rb --- foreman-proxy/lib/proxy/dns/webservice.rb 1970-01-01 01:00:00.000000000 +0100 +++ foreman-proxy-patched/lib/proxy/dns/webservice.rb 2011-08-19 16:54:46.000000000 +0200 @@ -0,0 +1,75 @@ +require "proxy/dns" +require "net/http" + +module Proxy::DNS + class Webservice < Record + + include Proxy::Util + + def initialize options = {} + #TODO raise "Unable to find DNS ws - check your dns_ws_* settings" unless ping the host? + super(options) + @ws_add = options[:ws_add] + @ws_del = options[:ws_del] + end + + # create({ :fqdn => "node01.lab", :value => "192.168.100.2"} + # create({ :fqdn => "node01.lab", :value => "3.100.168.192.in- addr.arpa", + # :type => "PTR"} + def create + url="#{@ws_add}" + url = url.gsub("__TYPE__",@type) + url = url.gsub("__TTL__",@ttl) + case @type + when "A" + url = url.gsub("__FQDN__",@fqdn) + url = url.gsub("__IP__",@value) + when "PTR" + url = url.gsub("__IP__",@value) + url = url.gsub("__FQDN__",@fqdn) + end + logger.debug "DNS url add : #{url}" + uri=URI.parse(url) + req = Net::HTTP::Get.new(url) + res = Net::HTTP.start(uri.host,uri.port){|http| + http.request(req) + } + req = Net::HTTP::Get.new(url) + if res.body =~ /^ip=/ + logger.debug "DNS ws successfully added record" + else + logger.debug "DNS ws return error code #{res.body}" + raise Proxy::DNS::Error.new("Add record error #{res.body}") + end + end + + # remove({ :fqdn => "node01.lab", :value => "192.168.100.2"} + def remove + url="#{@ws_del}" + url = url.gsub("__TYPE__",@type) + logger.debug "DNS url del : #{@type}, #{@fqdn} - #{@value}" + case @type + when "A" + url = url.gsub("__FQDN__",@fqdn) + url = url.gsub("__IP__","") + when "PTR" + url = url.gsub("__FQDN__","") + url = url.gsub("__IP__",@value) + end + logger.debug "DNS url del : #{url}" + uri=URI.parse(url) + req = Net::HTTP::Get.new(url) + res = Net::HTTP.start(uri.host,uri.port){|http| + http.request(req) + } + req = Net::HTTP::Get.new(url) + if res.body !~ /ERROR/i + logger.debug "DNS ws successfully removed record" + else + logger.debug "DNS ws return error code #{res.body}" + raise Proxy::DNS::Error.new("Remove record error #{res.body}") + end + end + + end +end

> Hi,
>
> First of all, thanks for all your work on The Foreman. I'm currently
> installing it to manage my servers and I had to modify a bit the
> smartproxy so it could :
> - read/write the DHCP config in LDAP
> - call a webservice to add/delete a DNS record
>
> I'm not a good programmer and i don't know ruby very well but if
> anyone is interested by these functionalities, here is the code :
>

Awesome!
from a very quick glance its looks very good, if you like to to get your git
credit to the code, you may follow up on how to submit patches [1]

btw: what is the webservice, is it based on some how grown solution?

Thanks,
Ohad

[1] - Contribute - Foreman

··· On Sun, Aug 21, 2011 at 1:20 PM, john4862@laposte.net wrote:

John

diff -uNr foreman-proxy/config/settings.yml.example foreman-proxy-
patched/config/settings.yml.example
— foreman-proxy/config/settings.yml.example 2011-06-06
16:52:40.000000000 +0200
+++ foreman-proxy-patched/config/settings.yml.example 2011-08-19
18:14:19.000000000 +0200
@@ -29,6 +29,9 @@

Enable DNS management

:dns: false
+:dns_vendor: ws
+:dns_ws_add: “http://mgt-srv.infra.tld/ws/add_dns.cgi?
fqdn=FQDN&policy=__TYPE___force&ip=IP&ttl=TTL
+:dns_ws_del: “http://mgt-srv.infra.tld/ws/del_dns.cgi?
fqdn=FQDN&policy=__TYPE___force&ip=IP&ttl=TTL
#:dns_key: /etc/rndc.key

use this setting if you are managing a dns server which is not

localhost though this proxy
#:dns_server: dns.domain.com
@@ -36,7 +39,11 @@

Enable DHCP management

:dhcp: false

The vendor can be either isc or native_ms

-:dhcp_vendor: isc
+:dhcp_vendor: isc_ldap
+:dhcp_ldapuser: “cn=dhcpadmin,ou=dhcp,dc=infra,dc=tld”
+:dhcp_ldappassword: “password”
+:dhcp_ldaphost: ldap.infra.tld
+:dhcp_ldapbase: “cn=DHCP Config,ou=dhcp,dc=infra,dc=tld”

Settings for Ubuntu ISC

#:dhcp_config: /etc/dhcp3/dhcpd.conf
#:dhcp_leases: /var/lib/dhcp3/dhcpd.leases
diff -uNr foreman-proxy/lib/dhcp_api.rb foreman-proxy-patched/lib/
dhcp_api.rb
— foreman-proxy/lib/dhcp_api.rb 2011-06-06 16:52:40.000000000
+0200
+++ foreman-proxy-patched/lib/dhcp_api.rb 2011-08-18
15:32:41.000000000 +0200
@@ -9,9 +9,16 @@
and File.exist?(SETTINGS.dhcp_config) and File.exist?
(SETTINGS.dhcp_leases)
log_halt 400, "Unable to find the DHCP configuration or lease
files"
end

  •  @server = Proxy::DHCP::ISC.new({:name => "127.0.0.1",
    
  •  @server = Proxy::DHCP::ISC.new({:name => "127.0.1.1",
                                    :config =>
    

File.read(SETTINGS.dhcp_config),
:leases =>
File.read(SETTINGS.dhcp_leases)})

  • when “isc_ldap”
  •  require 'proxy/dhcp/server/isc_ldap'
    
  •  @server = Proxy::DHCP::ISC_LDAP.new({:name =>
    

SETTINGS.dhcp_ldaphost,

  •                                       :ldapuser =>
    

SETTINGS.dhcp_ldapuser,

  •                                       :ldappassword =>
    

SETTINGS.dhcp_ldappassword,

  •                                       :ldaphost =>
    

SETTINGS.dhcp_ldaphost,

  •                                       :ldapbase =>
    

SETTINGS.dhcp_ldapbase})
when "native_ms"
require ‘proxy/dhcp/server/native_ms’
@server = Proxy::DHCP::NativeMS.new(:server =>
SETTINGS.dhcp_server ? SETTINGS.dhcp_server : “127.0.0.1”)
diff -uNr foreman-proxy/lib/dns_api.rb foreman-proxy-patched/lib/
dns_api.rb
— foreman-proxy/lib/dns_api.rb 2011-06-06 16:52:40.000000000
+0200
+++ foreman-proxy-patched/lib/dns_api.rb 2011-08-1911:14:25.000000000
+0200
@@ -1,8 +1,14 @@
-require “proxy/dns/bind”

class SmartProxy
def setup(opts)

  • @server = Proxy::DNS::Bind.new(opts.merge(:server =>
    SETTINGS.dns_server))
  • case SETTINGS.dns_vendor.downcase
  • when “bind”
  •    require "proxy/dns/bind"
    
  •    @server = Proxy::DNS::Bind.new(opts.merge(:server =>
    

SETTINGS.dns_server))

  • when “ws”
  •    require "proxy/dns/webservice"
    
  •    @server = Proxy::DNS::Webservice.new(opts.merge({:ws_add =>
    

SETTINGS.dns_ws_add, :ws_del => SETTINGS.dns_ws_del}))

  • end
    end

post “/dns/” do
diff -uNr foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb foreman-
proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb
— foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb 1970-01-01
01:00:00.000000000 +0100
+++ foreman-proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb 2011-08-18
15:35:41.000000000 +0200
@@ -0,0 +1,189 @@
+require ‘time’
+require ‘net/ldap’
+
+module Proxy::DHCP

  • class ISC_LDAP < Server
  • def initialize options
  •  super(options[:name])
    
  •  @ldapuser = options[:ldapuser]
    
  •  @ldappassword = options[:ldappassword]
    
  •  @ldaphost = options[:ldaphost]
    
  •  @ldapbase = options[:ldapbase]
    
  • end
  • def delRecord subnet, record
  •  validate_subnet subnet
    
  •  validate_record record
    
  •  raise InvalidRecord, "#{record} is static - unable to delete"
    

unless record.deleteable?
+

  •  msg = "Removed DHCP reservation for #{record.name} =>
    

#{record}"

  •  ldapConnect
    
  •  filter = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet
    

#{record.mac}")

  •  @ldap.search(:base => @ldapbase, :filter => filter) do |entry|
    
  •      report "Removed DHCP LDAP record : #{entry.dn}"
    
  •      @ldap.delete :dn => entry.dn
    
  •  end
    
  •  subnet.delete record
    
  • end
  • def addRecord options = {}
  •  super(options)
    
  •  ip     = options[:ip]
    
  •  mac    = options[:mac]
    
  •  name   = options[:hostname]
    
  •  subnet = find_subnet(IPAddr.new(ip))
    
  •  msg    = "Added DHCP reservation for #{name}"
    
  •  ldapConnect
    
  •  dn = "cn=#{name},#{@ldapbase}"
    
  •  attr = {
    
  •      :cn => name,
    
  •      :objectclass => ["top", "dhcpHost"],
    
  •      :dhcpHWAddress => "ethernet "+mac,
    
  •      :dhcpStatements => ["fixed-address "+ip]
    
  •  }
    
  •  attr[:dhcpStatements].push("filename \"#{options[:filename]}
    

“”) if options[:filename]

  •  attr[:dhcpStatements].push("next-server
    

"+bootServer(options[:nextserver])) if options[:nextserver]
+

  •  @ldap.add(:dn => dn, :attributes => attr)
    
  •  Proxy::DHCP::Reservation.new(subnet, ip, mac, options)
    
  • end
  • def loadSubnetData subnet
  •  super
    
  •  ldapConnect
    
  •  filter = Net::LDAP::Filter.eq("objectClass","dhcpHost")
    
  •  @ldap.search(:base => @ldapbase, :filter =>
    

filter, :return_result => false) do |entry|

  •      opts = {}
    
  •      entry.each do |attribute, values|
    
  •          case attribute.to_s
    
  •          when "cn"
    
  •            opts[:title] = values
    
  •          when "dhcphwaddress"
    
  •            opts[:mac] = values.to_s.split[1]
    
  •          when "dhcpstatements"
    
  •              values.each do |value|
    
  •                  case value.to_s
    
  •                  when /^fixed-address\s+(\S+)/
    
  •                      opts[:ip] = $1
    
  •                  when /^filename\s+(\S+)/
    
  •                      opts[:filename] = $1
    
  •                  when /^next-server\s+(\S+)/
    
  •                      opts[:nextServer] = $1
    
  •                  end
    
  •              end
    
  •          end
    
  •       end
    
  •       begin
    
  •           Proxy::DHCP::Reservation.new(subnet, opts[:ip],
    

opts[:mac], opts) if subnet.include? opts[:ip]

  •       rescue Exception => e
    
  •           logger.warn "skipped #{title} - #{e}"
    
  •       end
    
  •   end
    
  •  report "Enumerated hosts on #{subnet.network}"
    
  • end
  • performance issue with find_record of Server class

  • def find_record record
  •  ldapConnect
    
  •  filter_ip = Net::LDAP::Filter.eq("dhcpStatements","fixed-
    

address #{record}")

  •  filter_mac = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet
    

#{record}")

  •  filter_name = Net::LDAP::Filter.eq("cn","#{record}")
    
  •  filter = filter_ip | filter_mac | filter_name
    
  •  @ldap.search(:base => @ldapbase, :filter => filter) do |entry|
    
  •      return record
    
  •  end
    
  •  return nil
    
  • end
  • private
  • def ldapConnect
  •  @ldap = Net::LDAP.new :host => @ldaphost, :port => 389,
    
  •                       :auth => {
    
  •                           :method => :simple,
    
  •                           :username => @ldapuser,
    
  •                           :password => @ldappassword
    
  •                       }
    
  • end
  • def loadSubnets
  •  super
    
  •  ldapConnect
    
  •  filter = Net::LDAP::Filter.eq("objectClass","dhcpSubnet")
    
  •  @ldap.search(:base => @ldapbase, :filter =>
    

filter, :return_result => false) do |entry|

  •      opts = {}
    
  •      entry.each do |attribute, values|
    
  •          case attribute.to_s
    
  •          when "cn"
    
  •              opts[:subnet] = values.to_s
    
  •          when "dhcpoption"
    
  •              values.each do |value|
    
  •                  if value.to_s =~ /^subnet-mask\s+([\d\.]+)/
    
  •                      opts[:subnetmask] = $1
    
  •                  end
    
  •              end
    
  •          end
    
  •       end
    
  •       report "subnet new : "+opts[:subnet]+opts[:subnetmask]
    
  •       Proxy::DHCP::Subnet.new(self, opts[:subnet],
    

opts[:subnetmask])

  •    end
    
  •  "Enumerated the scopes on #{@name}"
    
  • end
  • def report msg, response=""
  •  if response.nil? or !response.grep(/can't|no more|not connected|
    

Syntax error/).empty?

  •    logger.error "Omshell failed:\n" + (response.nil? ? "No
    

response from DHCP server" : response.join(", "))

  •    msg.sub! /Removed/,    "remove"
    
  •    msg.sub! /Added/,      "add"
    
  •    msg.sub! /Enumerated/, "enumerate"
    
  •    msg  = "Failed to #{msg}"
    
  •    msg += ": Entry already exists" if response and
    

response.grep(/object: already exists/).size > 0

  •    msg += ": No response from DHCP server" if response.nil? or
    

response.grep(/not connected/).size > 0

  •    raise Proxy::DHCP::Error.new(msg)
    
  •  else
    
  •    logger.info msg
    
  •  end
    
  • end
  • def ip2hex ip
  •  ip.split(".").map{|i| "%02x" % i }.join(":")
    
  • end
  • def hex2ip hex
  •  hex.split(":").map{|h| h.to_i(16).to_s}.join(".")
    
  • end
  • def find_record_by_title subnet, title
  •  subnet.records.each do |v|
    
  •    return v if v.options[:title] == title
    
  •  end
    
  • end
  • ISC stores timestamps in UTC, therefor forcing the time to load

from GMT/UTC TZ

  • def parse_time str
  •  Time.parse(str +" UTC")
    
  • rescue => e
  •  logger.warn "Unable to parse time #{e}"
    
  •  raise "Unable to parse time #{e}"
    
  • end
  • def bootServer server
  •  begin
    
  •    ns = validate_ip(server)
    
  •  rescue
    
  •    begin
    
  •      ns = Resolv.new.getaddress(server)
    
  •    rescue
    
  •      logger.warn "Failed to resolve IP address for #{server}"
    
  •      ns = "\\\"#{server}\\\""
    
  •    end
    
  •  end
    
  •  "#{ns}"
    
  • end
  • end
    +end
    diff -uNr foreman-proxy/lib/proxy/dns/webservice.rb foreman-proxy-
    patched/lib/proxy/dns/webservice.rb
    — foreman-proxy/lib/proxy/dns/webservice.rb 1970-01-01
    01:00:00.000000000 +0100
    +++ foreman-proxy-patched/lib/proxy/dns/webservice.rb 2011-08-19
    16:54:46.000000000 +0200
    @@ -0,0 +1,75 @@
    +require “proxy/dns”
    +require “net/http”

+module Proxy::DNS

  • class Webservice < Record
  • include Proxy::Util
  • def initialize options = {}
  •  #TODO raise "Unable to find DNS ws - check your dns_ws_*
    

settings" unless ping the host?

  •  super(options)
    
  •  @ws_add = options[:ws_add]
    
  •  @ws_del = options[:ws_del]
    
  • end
  • create({ :fqdn => “node01.lab”, :value => “192.168.100.2”}

  • create({ :fqdn => “node01.lab”, :value => "3.100.168.192.in-

addr.arpa",

  • :type => “PTR”}

  • def create
  •  url="#{@ws_add}"
    
  •  url = url.gsub("__TYPE__",@type)
    
  •  url = url.gsub("__TTL__",@ttl)
    
  •  case @type
    
  •  when "A"
    
  •    url = url.gsub("__FQDN__",@fqdn)
    
  •    url = url.gsub("__IP__",@value)
    
  •  when "PTR"
    
  •    url = url.gsub("__IP__",@value)
    
  •    url = url.gsub("__FQDN__",@fqdn)
    
  •  end
    
  •  logger.debug "DNS url add : #{url}"
    
  •  uri=URI.parse(url)
    
  •  req = Net::HTTP::Get.new(url)
    
  •  res = Net::HTTP.start(uri.host,uri.port){|http|
    
  •      http.request(req)
    
  •  }
    
  •  req = Net::HTTP::Get.new(url)
    
  •  if res.body =~ /^ip=/
    
  •    logger.debug "DNS ws successfully added record"
    
  •  else
    
  •    logger.debug "DNS ws return error code #{res.body}"
    
  •    raise Proxy::DNS::Error.new("Add record error #{res.body}")
    
  •  end
    
  • end
  • remove({ :fqdn => “node01.lab”, :value => “192.168.100.2”}

  • def remove
  •  url="#{@ws_del}"
    
  •  url = url.gsub("__TYPE__",@type)
    
  •  logger.debug "DNS url del : #{@type}, #{@fqdn} - #{@value}"
    
  •  case @type
    
  •  when "A"
    
  •    url = url.gsub("__FQDN__",@fqdn)
    
  •    url = url.gsub("__IP__","")
    
  •  when "PTR"
    
  •    url = url.gsub("__FQDN__","")
    
  •    url = url.gsub("__IP__",@value)
    
  •  end
    
  •  logger.debug "DNS url del : #{url}"
    
  •  uri=URI.parse(url)
    
  •  req = Net::HTTP::Get.new(url)
    
  •  res = Net::HTTP.start(uri.host,uri.port){|http|
    
  •      http.request(req)
    
  •  }
    
  •  req = Net::HTTP::Get.new(url)
    
  •  if res.body !~ /ERROR/i
    
  •    logger.debug "DNS ws successfully removed record"
    
  •  else
    
  •    logger.debug "DNS ws return error code #{res.body}"
    
  •    raise Proxy::DNS::Error.new("Remove record error
    

#{res.body}")

  •  end
    
  • end
  • end
    +end


You received this message because you are subscribed to the Google Groups
"Foreman users" group.
To post to this group, send email to foreman-users@googlegroups.com.
To unsubscribe from this group, send email to
foreman-users+unsubscribe@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/foreman-users?hl=en.

> Message du 21/08/11 20:04
> De : "Ohad Levy"
> A : foreman-users@googlegroups.com
> Copie à :
> Objet : Re: [foreman-users] Foreman-proxy : patch to support dhcp-ldap and dns by webservice
>
>
> > Hi,
> >
> > First of all, thanks for all your work on The Foreman. I'm currently
> > installing it to manage my servers and I had to modify a bit the
> > smartproxy so it could :
> > - read/write the DHCP config in LDAP
> > - call a webservice to add/delete a DNS record
> >
> > I'm not a good programmer and i don't know ruby very well but if
> > anyone is interested by these functionalities, here is the code :
> >
>
> Awesome!
> from a very quick glance its looks very good, if you like to to get your git
> credit to the code, you may follow up on how to submit patches [1]
thanks, i'm gonna have a look :wink:

>
> btw: what is the webservice, is it based on some how grown solution?
Actually, we are using Powerdns so our DNS records can be stored in a MySQL database which is much more convenient than the Bind files. The manual DNS work (creating zones,…) is done with PowerDNS on Rails and our "webservice" uses this rails app to add/delete records.

>
> Thanks,
> Ohad
>
> [1] - Contribute - Foreman
>
> > John
> >
> > diff -uNr foreman-proxy/config/settings.yml.example foreman-proxy-
> > patched/config/settings.yml.example
> > — foreman-proxy/config/settings.yml.example 2011-06-06
> > 16:52:40.000000000 +0200
> > +++ foreman-proxy-patched/config/settings.yml.example 2011-08-19
> > 18:14:19.000000000 +0200
> > @@ -29,6 +29,9 @@
> >
> > # Enable DNS management
> > :dns: false
> > +:dns_vendor: ws
> > +:dns_ws_add: "http://mgt-srv.infra.tld/ws/add_dns.cgi?
> > fqdn=FQDN&policy=__TYPE___force&ip=IP&ttl=TTL"
> > +:dns_ws_del: "http://mgt-srv.infra.tld/ws/del_dns.cgi?
> > fqdn=FQDN&policy=_TYPE___force&ip=IP&ttl=TTL"
> > #:dns_key: /etc/rndc.key
> > # use this setting if you are managing a dns server which is not
> > localhost though this proxy
> > #:dns_server: dns.domain.com
> > @@ -36,7 +39,11 @@
> > # Enable DHCP management
> > :dhcp: false
> > # The vendor can be either isc or native_ms
> > -:dhcp_vendor: isc
> > +:dhcp_vendor: isc_ldap
> > +:dhcp_ldapuser: "cn=dhcpadmin,ou=dhcp,dc=infra,dc=tld"
> > +:dhcp_ldappassword: "password"
> > +:dhcp_ldaphost: ldap.infra.tld
> > +:dhcp_ldapbase: "cn=DHCP Config,ou=dhcp,dc=infra,dc=tld"
> > # Settings for Ubuntu ISC
> > #:dhcp_config: /etc/dhcp3/dhcpd.conf
> > #:dhcp_leases: /var/lib/dhcp3/dhcpd.leases
> > diff -uNr foreman-proxy/lib/dhcp_api.rb foreman-proxy-patched/lib/
> > dhcp_api.rb
> > — foreman-proxy/lib/dhcp_api.rb 2011-06-06 16:52:40.000000000
> > +0200
> > +++ foreman-proxy-patched/lib/dhcp_api.rb 2011-08-18
> > 15:32:41.000000000 +0200
> > @@ -9,9 +9,16 @@
> > and File.exist?(SETTINGS.dhcp_config) and File.exist?
> > (SETTINGS.dhcp_leases)
> > log_halt 400, "Unable to find the DHCP configuration or lease
> > files"
> > end
> > - @server = Proxy::DHCP::ISC.new({:name => "127.0.0.1",
> > + @server = Proxy::DHCP::ISC.new({:name => "127.0.1.1",
> > :config =>
> > File.read(SETTINGS.dhcp_config),
> > :leases =>
> > File.read(SETTINGS.dhcp_leases)})
> > + when "isc_ldap"
> > + require 'proxy/dhcp/server/isc_ldap'
> > + @server = Proxy::DHCP::ISC_LDAP.new({:name =>
> > SETTINGS.dhcp_ldaphost,
> > + :ldapuser =>
> > SETTINGS.dhcp_ldapuser,
> > + :ldappassword =>
> > SETTINGS.dhcp_ldappassword,
> > + :ldaphost =>
> > SETTINGS.dhcp_ldaphost,
> > + :ldapbase =>
> > SETTINGS.dhcp_ldapbase})
> > when "native_ms"
> > require 'proxy/dhcp/server/native_ms'
> > @server = Proxy::DHCP::NativeMS.new(:server =>
> > SETTINGS.dhcp_server ? SETTINGS.dhcp_server : "127.0.0.1")
> > diff -uNr foreman-proxy/lib/dns_api.rb foreman-proxy-patched/lib/
> > dns_api.rb
> > — foreman-proxy/lib/dns_api.rb 2011-06-06 16:52:40.000000000
> > +0200
> > +++ foreman-proxy-patched/lib/dns_api.rb 2011-08-1911:14:25.000000000
> > +0200
> > @@ -1,8 +1,14 @@
> > -require "proxy/dns/bind"
> >
> > class SmartProxy
> > def setup(opts)
> > - @server = Proxy::DNS::Bind.new(opts.merge(:server =>
> > SETTINGS.dns_server))
> > + case SETTINGS.dns_vendor.downcase
> > + when "bind"
> > + require "proxy/dns/bind"
> > + @server = Proxy::DNS::Bind.new(opts.merge(:server =>
> > SETTINGS.dns_server))
> > + when "ws"
> > + require "proxy/dns/webservice"
> > + @server = Proxy::DNS::Webservice.new(opts.merge({:ws_add =>
> > SETTINGS.dns_ws_add, :ws_del => SETTINGS.dns_ws_del}))
> > + end
> > end
> >
> > post "/dns/" do
> > diff -uNr foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb foreman-
> > proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb
> > — foreman-proxy/lib/proxy/dhcp/server/isc_ldap.rb 1970-01-01
> > 01:00:00.000000000 +0100
> > +++ foreman-proxy-patched/lib/proxy/dhcp/server/isc_ldap.rb 2011-08-18
> > 15:35:41.000000000 +0200
> > @@ -0,0 +1,189 @@
> > +require 'time'
> > +require 'net/ldap'
> > +
> > +module Proxy::DHCP
> > + class ISC_LDAP < Server
> > +
> > + def initialize options
> > + super(options[:name])
> > + @ldapuser = options[:ldapuser]
> > + @ldappassword = options[:ldappassword]
> > + @ldaphost = options[:ldaphost]
> > + @ldapbase = options[:ldapbase]
> > + end
> > +
> > + def delRecord subnet, record
> > + validate_subnet subnet
> > + validate_record record
> > + raise InvalidRecord, "#{record} is static - unable to delete"
> > unless record.deleteable?
> > +
> > + msg = "Removed DHCP reservation for #{record.name} =>
> > #{record}"
> > + ldapConnect
> > + filter = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet
> > #{record.mac}")
> > + @ldap.search(:base => @ldapbase, :filter => filter) do |entry|
> > + report "Removed DHCP LDAP record : #{entry.dn}"
> > + @ldap.delete :dn => entry.dn
> > + end
> > + subnet.delete record
> > + end
> > +
> > + def addRecord options = {}
> > + super(options)
> > + ip = options[:ip]
> > + mac = options[:mac]
> > + name = options[:hostname]
> > + subnet = find_subnet(IPAddr.new(ip))
> > + msg = "Added DHCP reservation for #{name}"
> > +
> > + ldapConnect
> > + dn = "cn=#{name},#{@ldapbase}"
> > + attr = {
> > + :cn => name,
> > + :objectclass => ["top", "dhcpHost"],
> > + :dhcpHWAddress => "ethernet "+mac,
> > + :dhcpStatements => ["fixed-address "+ip]
> > + }
> > + attr[:dhcpStatements].push("filename &quot;#{options[:filename]}
> > &quot;") if options[:filename]
> > + attr[:dhcpStatements].push("next-server
> > "+bootServer(options[:nextserver])) if options[:nextserver]
> > +
> > + @ldap.add(:dn => dn, :attributes => attr)
> > +
> > + Proxy::DHCP::Reservation.new(subnet, ip, mac, options)
> > + end
> > +
> > + def loadSubnetData subnet
> > + super
> > + ldapConnect
> > + filter = Net::LDAP::Filter.eq("objectClass","dhcpHost")
> > + @ldap.search(:base => @ldapbase, :filter =>
> > filter, :return_result => false) do |entry|
> > + opts = {}
> > + entry.each do |attribute, values|
> > + case attribute.to_s
> > + when "cn"
> > + opts[:title] = values
> > + when "dhcphwaddress"
> > + opts[:mac] = values.to_s.split[1]
> > + when "dhcpstatements"
> > + values.each do |value|
> > + case value.to_s
> > + when /^fixed-address\s+(\S+)/
> > + opts[:ip] = $1
> > + when /^filename\s+(\S+)/
> > + opts[:filename] = $1
> > + when /^next-server\s+(\S+)/
> > + opts[:nextServer] = $1
> > + end
> > + end
> > + end
> > + end
> > + begin
> > + Proxy::DHCP::Reservation.new(subnet, opts[:ip],
> > opts[:mac], opts) if subnet.include? opts[:ip]
> > + rescue Exception => e
> > + logger.warn "skipped #{title} - #{e}"
> > + end
> > + end
> > +
> > + report "Enumerated hosts on #{subnet.network}"
> > + end
> > +
> > + ## performance issue with find_record of Server class
> > + def find_record record
> > + ldapConnect
> > + filter_ip = Net::LDAP::Filter.eq("dhcpStatements","fixed-
> > address #{record}")
> > + filter_mac = Net::LDAP::Filter.eq("dhcpHWAddress","ethernet
> > #{record}")
> > + filter_name = Net::LDAP::Filter.eq("cn","#{record}")
> > + filter = filter_ip | filter_mac | filter_name
> > + @ldap.search(:base => @ldapbase, :filter => filter) do |entry|
> > + return record
> > + end
> > + return nil
> > + end
> > +
> > + private
> > + def ldapConnect
> > + @ldap = Net::LDAP.new :host => @ldaphost, :port => 389,
> > + :auth => {
> > + :method => :simple,
> > + :username => @ldapuser,
> > + :password => @ldappassword
> > + }
> > + end
> > +
> > + def loadSubnets
> > + super
> > + ldapConnect
> > + filter = Net::LDAP::Filter.eq("objectClass","dhcpSubnet")
> > + @ldap.search(:base => @ldapbase, :filter =>
> > filter, :return_result => false) do |entry|
> > + opts = {}
> > + entry.each do |attribute, values|
> > + case attribute.to_s
> > + when "cn"
> > + opts[:subnet] = values.to_s
> > + when "dhcpoption"
> > + values.each do |value|
> > + if value.to_s =~ /^subnet-mask\s+([\d.]+)/
> > + opts[:subnetmask] = $1
> > + end
> > + end
> > + end
> > + end
> > + report "subnet new : "+opts[:subnet]+opts[:subnetmask]
> > + Proxy::DHCP::Subnet.new(self, opts[:subnet],
> > opts[:subnetmask])
> > + end
> > + "Enumerated the scopes on #{@name}"
> > + end
> > +
> > +
> > + def report msg, response=""
> > + if response.nil? or !response.grep(/can't|no more|not connected|
> > Syntax error/).empty?
> > + logger.error "Omshell failed:\n" + (response.nil? ? "No
> > response from DHCP server" : response.join(", "))
> > + msg.sub! /Removed/, "remove"
> > + msg.sub! /Added/, "add"
> > + msg.sub! /Enumerated/, "enumerate"
> > + msg = "Failed to #{msg}"
> > + msg += ": Entry already exists" if response and
> > response.grep(/object: already exists/).size > 0
> > + msg += ": No response from DHCP server" if response.nil? or
> > response.grep(/not connected/).size > 0
> > + raise Proxy::DHCP::Error.new(msg)
> > + else
> > + logger.info msg
> > + end
> > + end
> > +
> > + def ip2hex ip
> > + ip.split(".").map{|i| "%02x" % i }.join(":")
> > + end
> > +
> > + def hex2ip hex
> > + hex.split(":").map{|h| h.to_i(16).to_s}.join(".")
> > + end
> > +
> > + def find_record_by_title subnet, title
> > + subnet.records.each do |v|
> > + return v if v.options[:title] == title
> > + end
> > + end
> > +
> > + # ISC stores timestamps in UTC, therefor forcing the time to load
> > from GMT/UTC TZ
> > + def parse_time str
> > + Time.parse(str +" UTC")
> > + rescue => e
> > + logger.warn "Unable to parse time #{e}"
> > + raise "Unable to parse time #{e}"
> > + end
> > +
> > + def bootServer server
> > + begin
> > + ns = validate_ip(server)
> > + rescue
> > + begin
> > + ns = Resolv.new.getaddress(server)
> > + rescue
> > + logger.warn "Failed to resolve IP address for #{server}"
> > + ns = "\&quot;#{server}\&quot;"
> > + end
> > + end
> > + "#{ns}"
> > + end
> > +
> > + end
> > +end
> > diff -uNr foreman-proxy/lib/proxy/dns/webservice.rb foreman-proxy-
> > patched/lib/proxy/dns/webservice.rb
> > — foreman-proxy/lib/proxy/dns/webservice.rb 1970-01-01
> > 01:00:00.000000000 +0100
> > +++ foreman-proxy-patched/lib/proxy/dns/webservice.rb 2011-08-19
> > 16:54:46.000000000 +0200
> > @@ -0,0 +1,75 @@
> > +require "proxy/dns"
> > +require "net/http"
> > +
> > +module Proxy::DNS
> > + class Webservice < Record
> > +
> > + include Proxy::Util
> > +
> > + def initialize options = {}
> > + #TODO raise "Unable to find DNS ws - check your dns_ws
*
> > settings" unless ping the host?
> > + super(options)
> > + @ws_add = options[:ws_add]
> > + @ws_del = options[:ws_del]
> > + end
> > +
> > + # create({ :fqdn => "node01.lab", :value => "192.168.100.2"}
> > + # create({ :fqdn => "node01.lab", :value => "3.100.168.192.in-
> > addr.arpa",
> > + # :type => "PTR"}
> > + def create
> > + url="#{@ws_add}"
> > + url = url.gsub("TYPE",@type)
> > + url = url.gsub("TTL",@ttl)
> > + case @type
> > + when "A"
> > + url = url.gsub("FQDN",@fqdn)
> > + url = url.gsub("IP",@value)
> > + when "PTR"
> > + url = url.gsub("IP",@value)
> > + url = url.gsub("FQDN",@fqdn)
> > + end
> > + logger.debug "DNS url add : #{url}"
> > + uri=URI.parse(url)
> > + req = Net::HTTP::Get.new(url)
> > + res = Net::HTTP.start(uri.host,uri.port){|http|
> > + http.request(req)
> > + }
> > + req = Net::HTTP::Get.new(url)
> > + if res.body =~ /^ip=/
> > + logger.debug "DNS ws successfully added record"
> > + else
> > + logger.debug "DNS ws return error code #{res.body}"
> > + raise Proxy::DNS::Error.new("Add record error #{res.body}")
> > + end
> > + end
> > +
> > + # remove({ :fqdn => "node01.lab", :value => "192.168.100.2"}
> > + def remove
> > + url="#{@ws_del}"
> > + url = url.gsub("TYPE",@type)
> > + logger.debug "DNS url del : #{@type}, #{@fqdn} - #{@value}"
> > + case @type
> > + when "A"
> > + url = url.gsub("FQDN",@fqdn)
> > + url = url.gsub("IP","")
> > + when "PTR"
> > + url = url.gsub("FQDN","")
> > + url = url.gsub("IP",@value)
> > + end
> > + logger.debug "DNS url del : #{url}"
> > + uri=URI.parse(url)
> > + req = Net::HTTP::Get.new(url)
> > + res = Net::HTTP.start(uri.host,uri.port){|http|
> > + http.request(req)
> > + }
> > + req = Net::HTTP::Get.new(url)
> > + if res.body !~ /ERROR/i
> > + logger.debug "DNS ws successfully removed record"
> > + else
> > + logger.debug "DNS ws return error code #{res.body}"
> > + raise Proxy::DNS::Error.new("Remove record error
> > #{res.body}")
> > + end
> > + end
> > +
> > + end
> > +end
> >
> > –
> > You received this message because you are subscribed to the Google Groups
> > "Foreman users" group.
> > To post to this group, send email to foreman-users@googlegroups.com.
> > To unsubscribe from this group, send email to
> > foreman-users+unsubscribe@googlegroups.com.
> > For more options, visit this group at
> > http://groups.google.com/group/foreman-users?hl=en.
> >
> >
>
> –
> You received this message because you are subscribed to the Google Groups "Foreman users" group.
> To post to this group, send email to foreman-users@googlegroups.com.
> To unsubscribe from this group, send email to foreman-users+unsubscribe@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/foreman-users?hl=en.
>
>

Une messagerie gratuite, garantie à vie et des services en plus, ça vous tente ?
Je crée ma boîte mail www.laposte.net

··· > On Sun, Aug 21, 2011 at 1:20 PM, john4862@laposte.net > wrote: