diff options
| author | mo khan <mo@mokhan.ca> | 2025-02-27 11:39:38 -0700 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-02-27 11:39:38 -0700 |
| commit | fc35bf1ab821445b71daf4fb25f303e29866d46b (patch) | |
| tree | 2be3609c9d08ae38a75f251a1bbc4b08bf61ccec | |
| parent | f9f9dce24d4099ab919d605447c9928b5734e365 (diff) | |
Complete the first portion of a saml transaction
| -rw-r--r-- | src/idp/README.md | 13 | ||||
| -rw-r--r-- | src/idp/cert.pem | 20 | ||||
| -rw-r--r-- | src/idp/config.example.yml | 3 | ||||
| -rw-r--r-- | src/idp/insecure-key.pem | 27 | ||||
| -rwxr-xr-x | src/idp/main.rb | 57 | ||||
| -rw-r--r-- | src/sp/README.md | 16 | ||||
| -rw-r--r-- | src/sp/main.rb | 129 |
7 files changed, 159 insertions, 106 deletions
diff --git a/src/idp/README.md b/src/idp/README.md index 4452fa9..a9d8f2a 100644 --- a/src/idp/README.md +++ b/src/idp/README.md @@ -5,17 +5,8 @@ a SAML Service Provider ## Getting Started -1. Copy the example coniguration - - $ cp config.example.yml config.yml - -1. Edit the `config.yml` to match your needs. 1. Start the server: - $ ruby -rwebrick main.rb - -1. Start ngrok - - $ ngrok http 8282 + $ ruby main.rb -1. Use `https://<xxxx>.ngrok.io/metadata.xml` as your SAML IDP Metadata url. +1. Use `http://localhost:8282/metadata.xml` as your SAML IdP Metadata url. diff --git a/src/idp/cert.pem b/src/idp/cert.pem deleted file mode 100644 index 631b8a7..0000000 --- a/src/idp/cert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJDQTEL -MAkGA1UECAwCQUIxEDAOBgNVBAcMB0NhbGdhcnkxDzANBgNVBAoMBlhtbEtpdDEP -MA0GA1UECwwGWG1sS2l0MQ8wDQYDVQQDDAZYbWxLaXQwHhcNMjIwMzE4MjAzMTMy -WhcNMjIwNDE3MjAzMTMyWjBfMQswCQYDVQQGEwJDQTELMAkGA1UECAwCQUIxEDAO -BgNVBAcMB0NhbGdhcnkxDzANBgNVBAoMBlhtbEtpdDEPMA0GA1UECwwGWG1sS2l0 -MQ8wDQYDVQQDDAZYbWxLaXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDEqKaalD1hckzdERHCgn92KaeVX6VBAKw3eTo3jiMu1P+bd63sot1ClgJOfGEV -l1aDcflHuc229q+HMU0qzqzlvFra42Y1peZXEu01q2M4ZMG9VQI/p2u+cgXrTyBh -+NQMXUgCAwkywp1Et72GkRxkqyQCSM9vAMkwaVjek68zgFUNGSw2ZrKoKJOUf6NR -l+VoF4Nw3ubfrS+D2F2yzymRRpN3vOrVbwUc18Zpxxhw/C2bYS1FKe4owqm6lkvy -XKjhvUZ+ursU5qROCJck6EsOsbgoA2GoMfuOMkivkUKXVg8Cv8Z59+/1+6u8oSeG -1ITr0eMfFf6tShTS8UO1M7yXAgMBAAGjITAfMB0GA1UdDgQWBBT3q/PhlXnHI0St -QeVGiX2ZmlVRdzANBgkqhkiG9w0BAQsFAAOCAQEAO2tlOzw4KYo+O36xA3lOYEo5 -Swh5nYhaV1A/RBDBr9sA2wwcRLVU27xuLKu8a7fcN2pGpzrfYyQ6vmDIUfGUVdMT -a0AkHsdrZwn7TUtKpyrc/7zkIG3a26oDpVXdFpQnjoog5gNix2f3SWHYMgGOgLUd -DtyNh/LQpKTfU6wY50FKqpu/K8cLs0NS0yGmBmd2D1gQXcnY6Ng7K5fA+x3SdMI1 -wVupDCfX4RaWkTK1hnJt/NYsCO6TYp0ltP/Omhv/PDi8C/27wIY9uZ4DaK9vUIQv -gFO5n+bebIefpJYc0Q8iIFNY4am0DcendxWZSBK2aCWMJUF9H5xaej9a7BBDXw== ------END CERTIFICATE----- diff --git a/src/idp/config.example.yml b/src/idp/config.example.yml deleted file mode 100644 index 165b040..0000000 --- a/src/idp/config.example.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -host: example.ngrok.io -email: example@example.com diff --git a/src/idp/insecure-key.pem b/src/idp/insecure-key.pem deleted file mode 100644 index d7be441..0000000 --- a/src/idp/insecure-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAxKimmpQ9YXJM3RERwoJ/dimnlV+lQQCsN3k6N44jLtT/m3et -7KLdQpYCTnxhFZdWg3H5R7nNtvavhzFNKs6s5bxa2uNmNaXmVxLtNatjOGTBvVUC -P6drvnIF608gYfjUDF1IAgMJMsKdRLe9hpEcZKskAkjPbwDJMGlY3pOvM4BVDRks -NmayqCiTlH+jUZflaBeDcN7m360vg9hdss8pkUaTd7zq1W8FHNfGaccYcPwtm2Et -RSnuKMKpupZL8lyo4b1Gfrq7FOakTgiXJOhLDrG4KANhqDH7jjJIr5FCl1YPAr/G -effv9furvKEnhtSE69HjHxX+rUoU0vFDtTO8lwIDAQABAoIBACuVJLcFO0UpS5eC -fOkaepz5RkZ4V+s79u6kUx6UxX9PfQY7U7Qps9dZ31D9h5Z9X5Lp41DeAJUXvna7 -mlpuSyruv0PbOX+SMKYDb8aBIRASZE1NVZ49wEcIhf9MHeUYfAXxdk/b1GIHd0sP -XVVBO4Wj1+sZr77t8ahk8GkDWcSTvLFhGYsgtWfag4aj1YejxBtKGsYsOMCTZbd4 -A5lYT1dCyfsAo4b3bm6ajjdFoJUeC+SFsDzzbRWGyeRiCMzEBmksEEcMYKRo8uOr -JYgl6Wc2SBWZL80+d9s22ZHvE7FAmgWn6OQQhy+kR8w5UO52GocwnuNuDm6qVeu5 -66gc6iECgYEA7AI2OiFUneLZ3Ps5QFA4uDLkc7ny58pbLtY57jZvkPvTpvYZkYSu -x9JPxvSqCmS3hH9HEEE2Y+Tsg/mHEphY6t7REl2InqH3sLaeHQxMfHf9+LcKCPfl -OIr5yyaZRVB32nW14VukLYsmEQLJYLXaGVQ/sTYRn7Yf5/PKlectIOMCgYEA1VEl -u14AqEFTTpXC0sNySaM3m5buRYCx/Ro8K1riuKuwzfJT6rOdqvWghNGeFGpYPi4x -k+i+IfkItYbBA6YS6kROh/l9jkyNGrXWd8gsYiI8L3lpfl2PBCDs9349nl9l8DFZ -cvkTeRcVxd2RmtNkcNtpMgzjD2ycYaSQqEUGx70CgYEAreKbTY0NKR7g4d3/OpFg -mOZ2R4WzoHAJaqLQH+DfpnTEZnlgMUUO+Y7M1IujVPEL/YVBOIqzpjoewMXybRLu -QG5WoC9l32r6caq7KC/Nks9dwggqTp1Gt7g9fx47Q0ScacrcbOP2PNAPBe2FrcmO -nabjHo/1wDSRoXaPxo6DQ30CgYAU2de8VtXtnGUOO2lNvLkBJakb1kb4GDpNqTDU -dA/RSUcA+nzlZiU1Pskv8mVnTXXOrik+cfOT0ondZIydVLBoocCjXem97RGl2Lxb -/P8JoJsNcOq05WRDXQyMrJRNVLncHpbFvD8BCRahvqSq45rfxTKlJ8lSCqXGjZVu -PUEKaQKBgQCtV2eiirkXM05jIext/hEIuWcnvqr2ea0STha27ZZ7xkF93142GMCX -0I3b0VszWZj0911SOqUyMCeAKJg9PCz8kNZYZgOe17/bdXSacJJOtDGHgPeucA9Y -0csGOGCIspHia9MkxpSuvbE/OMa3F4e96Mm50hX/4MWMmyp4ANeSTg== ------END RSA PRIVATE KEY----- diff --git a/src/idp/main.rb b/src/idp/main.rb index 1b18f54..daa15cc 100755 --- a/src/idp/main.rb +++ b/src/idp/main.rb @@ -8,40 +8,12 @@ require "bundler/inline" gemfile do source "https://rubygems.org" + gem "erb", "~> 4.0" gem "rack", "~> 3.0" gem "rackup", "~> 2.0" gem "saml-kit", "~> 1.0" gem "webrick", "~> 1.0" end -require "erb" - -class Configuration - def initialize - @config = YAML.safe_load(read_from("config.yml")) - end - - def [](key) - @config.fetch(key.to_s) - end - - def private_key - @private_key ||= read_from('insecure-key.pem') - end - - def certificate - @certificate ||= read_from('cert.pem') - end - - private - - def base_dir - @base_dir ||= Pathname.new(__FILE__).parent - end - - def read_from(file) - base_dir.join(file).read - end -end class User def initialize(attributes) @@ -49,7 +21,7 @@ class User end def name_id_for(name_id_format) - @attributes.fetch(:email) + @attributes[:email] end def assertion_attributes_for(request) @@ -61,22 +33,18 @@ end class OnDemandRegistry < Saml::Kit::DefaultRegistry def metadata_for(entity_id) - puts entity_id.inspect + found = super(entity_id) + return found if found + + register_url(entity_id, verify_ssl: false) super(entity_id) end end -$config = Configuration.new - Saml::Kit.configure do |x| - x.entity_id = "https://#{$config[:host]}/metadata.xml" + x.entity_id = "http://localhost:8282/metadata.xml" x.registry = OnDemandRegistry.new x.logger = Logger.new("/dev/stderr") - x.add_key_pair( - $config.certificate, - $config.private_key, - use: :signing - ) end class IdentityProvider @@ -89,12 +57,11 @@ class IdentityProvider # GET /metadata.xml def metadata xml = Saml::Kit::Metadata.build_xml do |builder| - builder.embed_signature = false builder.contact_email = 'hi@example.com' builder.organization_name = "Acme, Inc" builder.organization_url = "https://example.com" builder.build_identity_provider do |x| - x.add_single_sign_on_service("https://#{$config[:host]}/sessions/new", binding: :http_post) + x.add_single_sign_on_service("http://localhost:8282/sessions/new", binding: :http_post) x.name_id_formats = [Saml::Kit::Namespaces::EMAIL_ADDRESS] x.attributes << :Username end @@ -133,7 +100,7 @@ class IdentityProvider saml_request = binding_for(request).deserialize(params) @builder = nil url, saml_params = saml_request.response_for( - User.new($config), + User.new({ email: "example@example.com" }), binding: :http_post, relay_state: params[:RelayState] ) do |builder| @@ -145,10 +112,10 @@ class IdentityProvider <html> <head><title></title></head> <body> - <h2>SAML Request</h2> + <h2>Recieved SAML Request</h2> <textarea readonly="readonly" disabled="disabled" cols=225 rows=6><%=- saml_request.to_xml(pretty: true) -%></textarea> - <h2>SAML Response</h2> + <h2>Sending SAML Response (IdP -> SP)</h2> <textarea readonly="readonly" disabled="disabled" cols=225 rows=30><%=- @builder.build.to_xml(pretty: true) -%></textarea> <form action="<%= url %>" method="post"> <%- saml_params.each do |(key, value)| -%> @@ -183,7 +150,7 @@ class IdentityProvider end def binding_for(request) - location = "#{$config[:host]}/sessions/new" + location = "http://localhost:8282/sessions/new" if request.post? Saml::Kit::Bindings::HttpPost .new(location: location) diff --git a/src/sp/README.md b/src/sp/README.md new file mode 100644 index 0000000..c7894a6 --- /dev/null +++ b/src/sp/README.md @@ -0,0 +1,16 @@ +# SAML SP + +This is a tiny SAML Service Provider for testing out interactions with +a SAML Identity Provider (IdP) + +## Getting Started + +1. Start the server: + + $ ruby main.rb + +1. Start ngrok + + $ ngrok http 8283 + +1. Use `https://<xxxx>.ngrok.io/metadata.xml` as your SAML SP Metadata url. diff --git a/src/sp/main.rb b/src/sp/main.rb new file mode 100644 index 0000000..9ba9268 --- /dev/null +++ b/src/sp/main.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby + +# Start the server by running: +# +# $ ruby main.rb + +require "bundler/inline" +gemfile do + source "https://rubygems.org" + + gem "base64", "~> 0.1" + gem "erb", "~> 4.0" + gem "rack", "~> 3.0" + gem "rackup", "~> 2.0" + gem "saml-kit", "~> 1.0" + gem "webrick", "~> 1.0" +end + +class OnDemandRegistry < Saml::Kit::DefaultRegistry + def metadata_for(entity_id) + found = super(entity_id) + return found if found + + register_url(entity_id, verify_ssl: false) + super(entity_id) + end +end + +Saml::Kit.configure do |x| + x.entity_id = "http://localhost:8283/metadata.xml" + x.registry = OnDemandRegistry.new + x.logger = Logger.new("/dev/stderr") +end + +class ServiceProvider + def initialize + @storage = {} + end + + # Download IDP Metadata + # + # GET /metadata.xml + def metadata + xml = Saml::Kit::Metadata.build_xml do |builder| + builder.embed_signature = false + builder.contact_email = 'hi@example.com' + builder.organization_name = "Acme, Inc" + builder.organization_url = "https://example.com" + builder.build_service_provider do |x| + x.name_id_formats = [Saml::Kit::Namespaces::EMAIL_ADDRESS] + x.add_assertion_consumer_service("http://localhost:8283/assertions", binding: :http_post) + end + end + + [200, { 'Content-Type' => "application/samlmetadata+xml" }, [xml]] + end + + def call(env) + path = env['PATH_INFO'] + case env['REQUEST_METHOD'] + when 'GET' + case path + when "/metadata.xml" + return metadata + else + # TODO Generate a post to the IdP + return post_to_idp(Rack::Request.new(env)) + end + when 'POST' + case path + when "/assertions" + # TODO:: Render the SAMLResponse from the IdP + return not_found + else + return not_found + end + end + not_found + end + + private + + def not_found + [404, {}, []] + end + + def post_to_idp(request) + entity_id = 'http://localhost:8282/metadata.xml' + idp = Saml::Kit.registry.metadata_for(entity_id) + relay_state = Base64.strict_encode64(JSON.generate(redirect_to: '/dashboard')) + + @saml_builder = nil + uri, saml_params = idp.login_request_for(binding: :http_post, relay_state: relay_state) do |builder| + @saml_builder = builder + # builder.issuer = params[:issuer] if params[:issuer].present? + # builder.assertion_consumer_service_url = callback_url + end + + template = <<~ERB + <!doctype html> + <html> + <head><title></title></head> + <body style="background-color: pink;"> + <h2>Sending SAML Request (SP -> IdP)</h2> + <textarea readonly="readonly" disabled="disabled" cols=225 rows=6><%=- @saml_builder.to_xml(pretty: true) -%></textarea> + + <form action="<%= uri %>" method="post"> + <%- saml_params.each do |(key, value)| -%> + <input type="hidden" name="<%= key %>" value="<%= value %>" /> + <%- end -%> + <input type="submit" value="Submit" /> + </form> + </body> + </html> + ERB + erb = ERB.new(template, nil, trim_mode: '-') + html = erb.result(binding) + [200, { 'Content-Type' => "text/html" }, [html]] + end +end + +if __FILE__ == $0 + app = Rack::Builder.new do + use Rack::Reloader + run ServiceProvider.new + end.to_app + + Rackup::Server.start(app: app, Port: 8283) +end |
