Revision 45e05273
Added by Stephen Benjamin about 10 years ago
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
fixes #1809 - freeipa integration to smartproxy