Project

General

Profile

« Previous | Next » 

Revision 45e05273

Added by Stephen Benjamin about 10 years ago

fixes #1809 - freeipa integration to smartproxy

View differences:

bundler.d/krb5.rb
group :krb5 do
gem 'rkerberos', '>= 0.1.1'
gem 'gssapi'
end
config/settings.yml.example
:bmc: false
#:bmc_default_provider: freeipmi
# Manage joining realms e.g. FreeIPA
:realm: false
# Available providers:
# freeipa
:realm_provider: freeipa
# Authentication for Kerberos-based Realms
:realm_keytab: /etc/foreman-proxy/freeipa.keytab
:realm_principal: realm-proxy@EXAMPLE.COM
# FreeIPA specific settings
# Remove from DNS when deleting the FreeIPA entry
:freeipa_remove_dns: true
# Where our proxy log files are stored
# filename or STDOUT
:log_file: /var/log/foreman-proxy/proxy.log
foreman-proxy.spec
Requires: %{?scl_prefix}rubygem(json)
Requires: %{?scl_prefix}rubygem(rkerberos)
Requires: %{?scl_prefix}rubygem(rubyipmi)
Requires: %{?scl_prefix}rubygem(gssapi)
Requires: sudo
Requires: wget
Requires(pre): shadow-utils
lib/proxy.rb
module Proxy
MODULES = %w{dns dhcp tftp puppetca puppet bmc chefproxy}
MODULES = %w{dns dhcp tftp puppetca puppet bmc chefproxy realm}
VERSION = "1.5-develop"
require "checks"
......
require "fileutils"
require "pathname"
require "rubygems" if USE_GEMS # required for testing
require "rubygems" if USE_GEMS # required for testing
require "proxy/log"
require "proxy/util"
require "proxy/tftp" if SETTINGS.tftp
......
require "proxy/dhcp" if SETTINGS.dhcp
require "proxy/bmc" if SETTINGS.bmc
require "proxy/chefproxy" if SETTINGS.chefproxy
require "proxy/realm" if SETTINGS.realm
def self.features
MODULES.collect{|mod| mod if SETTINGS.send mod}.compact
lib/proxy/dns/nsupdate_gss.rb
require 'proxy/dns/nsupdate'
require 'rkerberos'
require 'proxy/kerberos'
module Proxy::DNS
class NsupdateGSS < Nsupdate
include Proxy::Kerberos
attr_reader :tsig_keytab, :tsig_principal
def initialize options = {}
......
end
def nsupdate cmd
init_krb5_ccache if cmd == "connect"
init_krb5_ccache(tsig_keytab, tsig_principal) if cmd == "connect"
super
end
private
def init_krb5_ccache
krb5 = Kerberos::Krb5.new
ccache = Kerberos::Krb5::CredentialsCache.new
logger.info "Requesting credentials for Kerberos principal #{tsig_principal} using keytab #{tsig_keytab}"
begin
krb5.get_init_creds_keytab tsig_principal, tsig_keytab, nil, ccache
rescue => e
logger.error "Failed to initialise credential cache from keytab: #{e}"
raise Proxy::DNS::Error.new("Unable to initialise Kerberos: #{e}")
end
logger.debug "Kerberos credential cache initialised with principal: #{ccache.primary_principal}"
end
end
end
lib/proxy/kerberos.rb
require 'rkerberos'
module Proxy::Kerberos
def init_krb5_ccache keytab, principal
krb5 = Kerberos::Krb5.new
ccache = Kerberos::Krb5::CredentialsCache.new
logger.info "Requesting credentials for Kerberos principal #{principal} using keytab #{keytab}"
begin
krb5.get_init_creds_keytab principal, keytab, nil, ccache
rescue => e
logger.error "Failed to initialise credential cache from keytab: #{e}"
raise "Failed to initailize credentials cache from keytab: #{e}"
end
logger.debug "Kerberos credential cache initialised with principal: #{ccache.primary_principal}"
end
end
lib/proxy/realm.rb
module Proxy::Realm
class Error < RuntimeError; end
class Client
include Proxy::Log
include Proxy::Util
end
end
lib/proxy/realm/freeipa.rb
require 'gssapi'
require 'helpers'
require 'proxy/kerberos'
require 'proxy/util'
require 'uri'
require 'xmlrpc/client'
module Proxy::Realm
class FreeIPA < Client
include Proxy::Kerberos
include Proxy::Util
IPA_CONFIG = "/etc/ipa/default.conf"
def initialize
errors = []
errors << "keytab not configured" unless SETTINGS.realm_keytab
errors << "keytab not found: #{SETTINGS.realm_keytab}" unless SETTINGS.realm_keytab && File.exist?(SETTINGS.realm_keytab)
errors << "principal not configured" unless SETTINGS.realm_principal
logger.info "freeipa: realm keytab is '#{SETTINGS.realm_keytab}' and using principal '#{SETTINGS.realm_principal}'"
# Get FreeIPA Configuration
if File.exist?(IPA_CONFIG)
File.readlines(IPA_CONFIG).each do |line|
if line =~ /xmlrpc_uri/
@ipa_server = URI.parse line.split("=")[1].strip
logger.info "freeipa: server is #{@ipa_server}"
elsif line =~ /realm/
@realm_name = line.split("=")[1].strip
logger.info "freeipa: realm #{@realm_name}"
end
end
else
errors << "unable to read FreeIPA configuration: #{IPA_CONFIG}"
end
errors << "unable to parse client configuration" unless @ipa_server && @realm_name
if errors.empty?
# Get krb5 token
init_krb5_ccache SETTINGS.realm_keytab, SETTINGS.realm_principal
gssapi = GSSAPI::Simple.new(@ipa_server.host, "HTTP")
token = gssapi.init_context
# FreeIPA API returns some nils, Ruby XML-RPC doesn't like this
XMLRPC::Config.module_eval { const_set(:ENABLE_NIL_PARSER, true) }
@ipa = XMLRPC::Client.new2(@ipa_server.to_s)
@ipa.http_header_extra={ 'Authorization'=>"Negotiate #{strict_encode64(token)}",
'Referer' => @ipa_server.to_s,
'Content-Type' => 'text/xml; charset=utf-8'
}
else
raise Proxy::Realm::Error.new errors.join(", ")
end
end
def check_realm realm
raise Proxy::Realm::Error.new "Unknown realm #{realm}" unless realm.casecmp(@realm_name).zero?
end
def create realm, params
check_realm realm
options = { :setattr => [] }
# Send params to FreeIPA, may want to send more than one in the future
%w(userclass).each do |attr|
options[:setattr] << "#{attr}=#{params[attr]}" if params.has_key? attr
end
# Determine if we're updating a host or creating a new one
if @ipa.call("host_find", [params[:hostname]])["count"].zero?
options.merge!(:random => 1, :force => 1)
operation = "host_add"
else
# If the host is being rebuilt, disable it in order to revoke existing certs, keytabs, etc.
if params[:rebuild] == "true"
begin
logger.info "Attempting to disable host #{params[:hostname]} in FreeIPA"
@ipa.call("host_disable", [params[:hostname]])
rescue => e
logger.info "Disabling failed for host #{params[:hostname]}: #{e}. Continuing anyway."
end
end
options.merge!(:random => 1)
operation = "host_mod"
end
begin
result = @ipa.call(operation, [params[:hostname]], options)
rescue => e
if e.message =~ /no modifications/
result = {"result" => {"message" => "nothing to do"}}
else
raise
end
end
JSON.pretty_generate(result["result"])
end
def delete realm, hostname
check_realm realm
JSON.pretty_generate(@ipa.call("host_del", [hostname], {"updatedns" => SETTINGS.freeipa_remove_dns}))
end
end
end
lib/proxy/util.rb
require 'open3'
require 'shellwords'
require 'base64'
module Proxy::Util
......
Shellwords.escape(command)
end
end
def strict_encode64(str)
if Base64.respond_to?(:strict_encode64)
Base64.strict_encode64(str)
else
Base64.encode64(str).delete("\n")
end
end
end
lib/realm_api.rb
class SmartProxy < Sinatra::Base
def realm_setup
raise "Smart Proxy is not configured to support Realm" unless SETTINGS.realm
case SETTINGS.realm_provider
when "freeipa"
require 'proxy/realm/freeipa'
@realm = Proxy::Realm::FreeIPA.new
else
log_halt 400, "Unrecognized or missing Realm provider: #{SETTINGS.realm_provider.nil? ? "MISSING" : SETTINGS.realm_provider}"
end
rescue => e
log_halt 400, e
end
before do
realm_setup if request.path_info =~ /realm/
end
post "/realm/:realm/?" do
begin
content_type :json
@realm.create params[:realm], params
rescue Exception => e
log_halt 400, e
end
end
delete "/realm/:realm/:hostname/?" do
begin
content_type :json
@realm.delete params[:realm], params[:hostname]
rescue Exception => e
log_halt 400, e
end
end
end
lib/smart_proxy.rb
require "bmc_api" if SETTINGS.bmc
require "chefproxy_api" if SETTINGS.chefproxy
require "resolv" if SETTINGS.trusted_hosts
require "realm_api" if SETTINGS.realm
begin
require "facter"
test/util_test.rb
require 'test_helper'
class ProxyUtilTest < Test::Unit::TestCase
class UtilClass; extend Proxy::Util; end
def test_util_should_support_path
assert Proxy::Util.instance_methods.include? RUBY_VERSION =~ /^1\.8/ ? "which" : :which
......
# ruby 1.9 seems to return nil for $? in open3
assert_equal t.join, RUBY_VERSION =~ /1\.8\.\d+/ ? 0 : nil
end
def test_strict_encode64
assert_equal "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=", UtilClass.strict_encode64("a"*50)
end
end

Also available in: Unified diff