Неверные метаданные Rails/Devise/SAML (не работает с PingFederate)
Позвольте мне предвосхитить этот вопрос, сказав, что я новичок в SAML и едва понимаю, как он работает.
Настройка
Я использую devise_saml_authenticatable
драгоценный камень с приложением Rails 4 для достижения единого входа. Приложение Rails выступает в качестве поставщика услуг (SP). Чтобы проверить свои настройки, я создал учетную запись разработчика OneLogin и настроил тестовый соединитель SAML (IdP w/attr w/ sign response), используя следующие атрибуты:
Вкладка конфигурации
- Аудитория: mysubdomain.onelogin.com
- Получатель: http://mysubdomain.myapp.local:3000/saml/auth
- Валидатор URL ACS (потребителя): ^http://mysubdomain.myapp.local:3000/saml/auth$
- URL-адрес ACS (потребителя): http://mysubdomain.myapp.local:3000/saml/auth
- URL единого выхода: http://mysubdomain.myapp.local:3000/saml/idp_sign_out
Вкладка SSO
- URL эмитента: https://app.onelogin.com/saml/metadata/589819
- Конечная точка SAML 2.0 (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-post/sso/589819
- Конечная точка SLO (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-redirect/slo/589819
- Алгоритм подписи SAML: SHA-1
- Отпечаток SHA: 60:9D:18:56:B9:80:D4:25:63:C1:CC:57:6D:B9:06:7C:78:BB:2C:F1
Сертификат X.509:
----- BEGIN CERTIFICATE ----- MIIEFzCCAv + gAwIBAgIUQYRVa1MQpUh0gJaznmXSF / SPqnowDQYJKoZIhvcNAQEF BQAwWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCEZpcm1QbGF5MRUwEwYDVQQLDAxP bmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgOTI1MzEwHhcN MTYwOTIxMTU0NzQwWhcNMjEwOTIyMTU0NzQwWjBYMQswCQYDVQQGEwJVUzERMA8G A1UECgwIRmlybVBsYXkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW T25lTG9naW4gQWNjb3VudCA5MjUzMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC AQoCggEBALGVgocBj0ciHM3uKlWIcofPhOtzfJw1XpAdNynAvPtbCl7WE5 + sLBoQ ZF + oZ7Dl + wRW6DHMJCl9DdKcOaQA6 / gr5bwt78IzZ8hWMoKQEPih + E0km6rKLYA8 M52vxtJxGs8Iqx60QvPEePQFMOA + xg73OExfM7W5LnXwNz / Pxgsr3lBif5oCC76j SaTCFroV + TSjfOaYMW / lZrsS79KRIzA9I5XwUBe3bC8bsfQmZXgddCrkQUNSGGaS 7 / jtFUlQ94 + LAL+l3yoAiNAE6+mt48qqmyLfkKibXvnZ8dwuO272wpY4fEM+vFRy pYrTajqvhY3hYIq8dLw3ominE5VECl8CAwEAAaOB2DCB1TAMBgNVHRMBAf8EAjAA MB0GA1UdDgQWBBSxiuvTPxwOhh2pupID+tuyKCeceTCBlQYDVR0jBIGNMIGKgBSx iuvTPxwOhh2pupID+tuyKCeceaFcpFowWDELMAkGA1UEBhMCVVMxETAPBgNVBAoM CEZpcm1QbGF5MRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZU XV Z2luIEFjY291bnQgOTI1MzGCFEGEVWtTEKVIdICWs55l0hf0j6p6MA4GA1UdDwEB /wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAYBe+5d3zpLZ7fcf3l3rXYeIxcpN+ 9D2YZCbxsrBhY2Am4YE9nN+RaJXeDqeRBNtpayCZVxfHnXexRo1n7wxwTmosiydi 9yE7SY2xZf+3feQreF25atnn4tzVhxYONaX1njZMIt/TNa7A9aeDfHSD+vwSuYYB hGxKT6HOkEAEBiXCZ/FcVNiB0D8bRwQhiJ3BTzXDfqHrmq8QYdn3Ejlqo62vMl6W XeMXUoyv6cUc64Ap6E+XtEQI1E8YB5R8GtTs3Y1Oa2dD6yWyCyVJ20+Hi7IWAqXC EfqstqXB7FoQ2rAt39cepnu1SOarvEYDMwYIaVNF3hoyodBybJJsAwAnCQ== -----END CERTIFICATE-----
В моем devise.rb
У меня есть следующая конфигурация:
config.saml_create_user = false
config.saml_update_user = true
config.saml_default_user_key = :email
config.saml_session_index_key = :session_index
config.saml_use_subject = true
config.idp_settings_adapter = IdPSettingsAdapter
config.idp_entity_id_reader = DeviseSamlAuthenticatable::DefaultIdpEntityIdReader
Вот мой IdPSettingsAdapter
:
class IdPSettingsAdapter
def self.settings(idp_entity_id)
company = Company.find_by(idp_entity_id: idp_entity_id)
if company.present?
{
assertion_consumer_service_url: company.assertion_consumer_service_url,
assertion_consumer_service_binding: company.assertion_consumer_service_binding,
name_identifier_format: company.name_identifier_format,
issuer: company.issuer,
idp_entity_id: company.idp_entity_id,
authn_context: company.authn_context,
idp_slo_target_url: company.idp_slo_target_url,
idp_sso_target_url: company.idp_sso_target_url,
idp_cert_fingerprint: company.idp_cert_fingerprint
}
else
{}
end
end
end
Обратите внимание, что моя модель пользователя Contact
принадлежит Company
и что настройки единого входа хранятся в Company
модель.
Вот мои маршруты saml:
devise_for :contacts, skip: :saml_authenticatable, controllers: {
registrations: "registrations",
sessions: "sessions",
passwords: "passwords",
confirmations: "confirmations"
}
devise_scope :contact do
get '/sign_in' => 'sessions#new'
get '/sign_out' => 'sessions#destroy'
# SSO Routes
get 'saml/sign_in' => 'saml_sessions#new', as: :new_user_sso_session
post 'saml/auth' => 'saml_sessions#create', as: :user_sso_session
get 'saml/sign_out' => 'saml_sessions#destroy', as: :destroy_user_sso_session
get 'saml/metadata' => 'saml_sessions#metadata', as: :metadata_user_sso_session
match 'saml/idp_sign_out' => 'saml_sessions#idp_sign_out', via: [:get, :post]
end
Наконец, вот мой SamlSessionsController
:
require "ruby-saml"
class SamlSessionsController < SessionsController
include DeviseSamlAuthenticatable::SamlConfig
skip_before_filter :verify_authenticity_token, raise: false
before_action :authorize_viewer, except: [:metadata]
protect_from_forgery with: :null_session, except: :create
def new
idp_entity_id = Company.friendly.find(@_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
request = OneLogin::RubySaml::Authrequest.new
action = request.create(saml_config(idp_entity_id))
redirect_to action
end
def metadata
idp_entity_id = Company.friendly.find(@_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
meta = OneLogin::RubySaml::Metadata.new
render :xml => meta.generate(saml_config(idp_entity_id)), content_type: 'application/samlmetadata+xml'
end
def create
@idp_entity_id = Company.friendly.find(@_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_config(@idp_entity_id))
if !response.is_valid?
puts "SAML FAILED WITH ERROR: "
puts response.errors
end
super
end
def idp_sign_out
company = Company.friendly.find(request.subdomain.downcase)
idp_entity_id = Company.friendly.find(@_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
if params[:SAMLRequest] && Devise.saml_session_index_key
saml_config = saml_config(idp_entity_id)
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config(idp_entity_id))
resource_class.reset_session_key_for(logout_request.name_id)
# binding.pry
sign_out current_contact if contact_signed_in?
redirect_to company.after_slo_url.present? ? company.after_slo_url : 'https://' + company.issuer
# redirect_to generate_idp_logout_response(saml_config(idp_entity_id), logout_request.id)
elsif params[:SAMLResponse]
#Currently Devise handles the session invalidation when the request is made.
#To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
#based on that.
if Devise.saml_sign_out_success_url
redirect_to Devise.saml_sign_out_success_url
else
redirect_to action: :new
end
else
head :invalid_request
end
end
protected
# Override devise to send user to IdP logout for SLO
def after_sign_out_path_for(_)
request = OneLogin::RubySaml::Logoutrequest.new
request.create(saml_config)
end
def generate_idp_logout_response(saml_config, logout_request_id)
OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil)
end
end
Эта проблема
Когда я вручную сохраняю настройки карты из моего адаптера OneLogin в мой Company
модель (см. скриншот), я могу аутентифицироваться как пользователь моего приложения, используя OneLogin в качестве провайдера идентификации (IdP). Однако теперь мне нужно предоставить клиенту метаданные XML, представляющие настройки приложения. Когда я иду в /saml/metadata.xml
Я получаю следующую конфигурацию, которая, по мнению моего клиента, неверна. Клиент не предложил никаких дополнительных подробностей о том, в чем проблема. Они используют PingFederate, если это имеет значение.
<?xml version='1.0' encoding='UTF-8'?>
<md:EntityDescriptor ID='_a3581975-b73d-4784-a106-bafd61e15f87' xmlns:md='urn:oasis:names:tc:SAML:2.0:metadata'>
<md:SPSSODescriptor AuthnRequestsSigned='false' WantAssertionsSigned='false' protocolSupportEnumeration='urn:oasis:names:tc:SAML:2.0:protocol'>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:AssertionConsumerService Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' Location='https://mysubdomain.myapp.local:3000/saml/auth' index='0' isDefault='true'/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
У меня вопрос, что я здесь делаю неправильно и как я могу это исправить? Как я уже сказал, я плохо понимаю, как работает SAML под капотом.
1 ответ
В XML-файле метаданных не определен EntityID. Если вы попытаетесь проверить XML на инструменте проверки, вы получите
Линия: 2 | Столбец: 0 -> Элемент '{urn:oasis:names:tc:SAML:2.0: метаданные}EntityDescriptor': атрибут entityID является обязательным, но отсутствует.
Если вы просматриваете код ruby-saml, EntityID добавляется к метаданным XML, если определен файл settings.issuer. Можете ли вы проверить, предоставлены ли эти данные? Возможно, company.issuer, который я вижу в классе IdPSettingsAdapter, имеет пустое значение.