diff --git a/frontend.conf b/frontend.conf index c57abe9..48b7d48 100644 --- a/frontend.conf +++ b/frontend.conf @@ -1,7 +1,10 @@ +# Sets the maximum size of the variables hash table. +variables_hash_max_size 2048; + # This is the backend application we are protecting with SAML SSO upstream my_backend { zone my_backend 64k; - server localhost:8088; + server localhost:8011; } # Custom log format to include the 'NameID' subject in the REMOTE_USER field @@ -16,9 +19,9 @@ server { include conf.d/saml_sp.server_conf; # Reduce severity level as required - error_log /var/log/nginx/error.log debug; + error_log logs/error.log debug; - listen 8010; # Use SSL/TLS in production + listen 80; # Use SSL/TLS in production location / { # When a user is not authenticated (i.e., the "saml_access_granted" @@ -36,7 +39,7 @@ server { proxy_pass http://my_backend; # The backend site/app - access_log /var/log/nginx/access.log saml_sso; + access_log logs/access.log saml_sso; } } diff --git a/saml_sp_configuration.conf b/saml_sp_configuration.conf index 0edd87a..8995282 100644 --- a/saml_sp_configuration.conf +++ b/saml_sp_configuration.conf @@ -6,14 +6,14 @@ map $host $saml_sp_entity_id { # Unique identifier that identifies the SP to the IdP. # Must be URL or URN. - default "http://sp.example.com"; + default "http://sp.route443.dev"; } map $host $saml_sp_acs_url { # The ACS URL, an endpoint on the SP where the IdP # will redirect to with its authentication response. # Must match the ACS location defined in the "saml_sp.serer_conf" file. - default "http://sp.example.com:8010/saml/acs"; + default "http://sp.route443.dev/saml/acs"; } map $host $saml_sp_request_binding { @@ -31,13 +31,13 @@ map $host $saml_sp_sign_authn { map $host $saml_sp_signing_key { # Maps SP to the private key file that will be used to sign # the AuthnRequest or LogoutRequest sent to the IdP. - default "conf.d/saml_sp_sign.key"; + default "conf.d/saml.key"; } map $host $saml_sp_decryption_key { # Specifies the private key that the SP uses to decrypt encrypted assertion # or NameID from the IdP. - default ""; + default "conf.d/saml.key"; } map $host $saml_sp_force_authn { @@ -50,7 +50,7 @@ map $host $saml_sp_nameid_format { # generated by the IdP. Check section 8.3 of the SAML 2.0 Core specification # (http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) # for the list of allowed NameID Formats. - default "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + default "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; } map $host $saml_sp_relay_state { @@ -80,13 +80,13 @@ map $host $saml_sp_want_encrypted_assertion { map $host $saml_idp_entity_id { # Unique identifier that identifies the IdP to the SP. # Must be URL or URN. - default "http://idp.example.com"; + default "http://idp.route443.dev:8080/simplesaml/saml2/idp/metadata.php"; } map $host $saml_idp_sso_url { # IdP endpoint that the SP will send the SAML AuthnRequest to initiate # an authentication process. - default "http://idp.example.com:8080/saml2/idp"; + default "http://idp.route443.dev:8080/simplesaml/saml2/idp/SSOService.php"; } map $host $saml_idp_verification_certificate { @@ -94,7 +94,7 @@ map $host $saml_idp_verification_certificate { # on the SAML Response, LogoutRequest or LogoutResponse received from IdP. # Must be public key in PKCS#1 format. See documentation on how to convert # X.509 PEM to DER format. - default "conf.d/saml_idp_verify.spki"; + default "conf.d/saml.spki"; } ######### Single Logout (SLO) ######### @@ -102,7 +102,7 @@ map $host $saml_idp_verification_certificate { map $host $saml_sp_slo_url { # SP endpoint that the IdP will send the SAML LogoutRequest to initiate # a logout process or LogoutResponse to confirm the logout. - default "http://sp.example.com:8010/saml/sls"; + default "http://sp.route443.dev/saml/sls"; } map $host $saml_sp_slo_binding { @@ -124,7 +124,7 @@ map $host $saml_idp_slo_url { # If not set, the SAML Single Logout (SLO) feature is DISABLED and # requests to the 'logout' location will result in the termination # of the user session and a redirect to the logout landing page. - default "http://idp.example.com:8080/saml2/idp/sls"; + default "http://idp.route443.dev:8080/simplesaml/saml2/idp/SingleLogoutService.php"; } map $host $saml_sp_want_signed_slo { @@ -139,6 +139,10 @@ map $host $saml_logout_landing_page { default "/_logout"; # Built-in, simple logout page } +map $host $saml_debug { + default "1"; +} + map $proto $saml_cookie_flags { http "Path=/; SameSite=lax;"; # For HTTP/plaintext testing https "Path=/; SameSite=lax; HttpOnly; Secure;"; # Production recommendation @@ -163,37 +167,37 @@ map $http_x_forwarded_proto $proto { # to prevent replay attacks. (REQUIRED) # Timeout determines how long the SP waits for a response from the IDP, # i.e. how long the user authentication process can take. -keyval_zone zone=saml_request_id:1M state=/var/lib/nginx/state/saml_request_id.json timeout=5m; +keyval_zone zone=saml_request_id:1M state=conf.d/saml_request_id.json timeout=5m; # Zone for storing SAML Response message identifiers (ID) to prevent replay attacks. (REQUIRED) # Timeout determines how long the SP keeps IDs to prevent reuse. -keyval_zone zone=saml_response_id:1M state=/var/lib/nginx/state/saml_response_id.json timeout=1h; +keyval_zone zone=saml_response_id:1M state=conf.d/saml_response_id.json timeout=1h; # Zone for storing SAML session access information. (REQUIRED) # Timeout determines how long the SP keeps session access decision (the session lifetime). -keyval_zone zone=saml_session_access:1M state=/var/lib/nginx/state/saml_session_access.json timeout=1h; +keyval_zone zone=saml_session_access:1M state=conf.d/saml_session_access.json timeout=1h; # Zone for storing SAML NameID values. (REQUIRED) # Timeout determines how long the SP keeps NameID values. Must be equal to session lifetime. -keyval_zone zone=saml_name_id:1M state=/var/lib/nginx/state/saml_name_id.json timeout=1h; +keyval_zone zone=saml_name_id:1M state=conf.d/saml_name_id.json timeout=1h; # Zone for storing SAML NameID format values. (REQUIRED) # Timeout determines how long the SP keeps NameID format values. Must be equal to session lifetime. -keyval_zone zone=saml_name_id_format:1M state=/var/lib/nginx/state/saml_name_id_format.json timeout=1h; +keyval_zone zone=saml_name_id_format:1M state=conf.d/saml_name_id_format.json timeout=1h; # Zone for storing SAML SessionIndex values. (REQUIRED) # Timeout determines how long the SP keeps SessionIndex values. Must be equal to session lifetime. -keyval_zone zone=saml_session_index:1M state=/var/lib/nginx/state/saml_session_index.json timeout=1h; +keyval_zone zone=saml_session_index:1M state=conf.d/saml_session_index.json timeout=1h; # Zone for storing SAML AuthnContextClassRef values. (REQUIRED) # Timeout determines how long the SP keeps AuthnContextClassRef values. Must be equal to session lifetime. -keyval_zone zone=saml_authn_context_class_ref:1M state=/var/lib/nginx/state/saml_authn_context_class_ref.json timeout=1h; +keyval_zone zone=saml_authn_context_class_ref:1M state=conf.d/saml_authn_context_class_ref.json timeout=1h; # Zones for storing SAML attributes values. (OPTIONAL) # Timeout determines how long the SP keeps attributes values. Must be equal to session lifetime. -keyval_zone zone=saml_attrib_uid:1M state=/var/lib/nginx/state/saml_attrib_uid.json timeout=1h; -keyval_zone zone=saml_attrib_name:1M state=/var/lib/nginx/state/saml_attrib_name.json timeout=1h; -keyval_zone zone=saml_attrib_memberOf:1M state=/var/lib/nginx/state/saml_attrib_memberOf.json timeout=1h; +keyval_zone zone=saml_attrib_uid:1M state=conf.d/saml_attrib_uid.json timeout=1h; +keyval_zone zone=saml_attrib_name:1M state=conf.d/saml_attrib_name.json timeout=1h; +keyval_zone zone=saml_attrib_memberOf:1M state=conf.d/saml_attrib_memberOf.json timeout=1h; ######### SAML-related variables whose value is looked up by the key (session cookie) in the key-value database. diff --git a/test/idp/Dockerfile b/test/idp/Dockerfile new file mode 100644 index 0000000..3178323 --- /dev/null +++ b/test/idp/Dockerfile @@ -0,0 +1,54 @@ +FROM php:8.2-apache + +# Utilities +RUN apt-get update \ + && apt-get -y install apt-transport-https git curl vim \ + memcached libmemcached-dev zlib1g-dev libxml2-dev --no-install-recommends \ + && apt-get clean -y \ + && pecl install memcached --with-libmemcached-dir=no \ + && docker-php-ext-enable memcached \ + && docker-php-ext-install soap \ + && docker-php-ext-enable soap \ + && rm -r /var/lib/apt/lists/* + +# SimpleSAMLphp +ARG SIMPLESAMLPHP_VERSION=1.19.7 +RUN curl -s -L -o /tmp/simplesamlphp.tar.gz https://github.com/simplesamlphp/simplesamlphp/releases/download/v$SIMPLESAMLPHP_VERSION/simplesamlphp-$SIMPLESAMLPHP_VERSION.tar.gz && \ + tar xzf /tmp/simplesamlphp.tar.gz -C /tmp && \ + rm -f /tmp/simplesamlphp.tar.gz && \ + mv /tmp/simplesamlphp-* /var/www/simplesamlphp && \ + touch /var/www/simplesamlphp/modules/exampleauth/enable +COPY saml.crt /var/www/simplesamlphp/cert/ +COPY saml.key /var/www/simplesamlphp/cert/ +COPY config/simplesamlphp/config.php /var/www/simplesamlphp/config +COPY config/simplesamlphp/authsources.php /var/www/simplesamlphp/config +COPY config/simplesamlphp/saml20-sp-remote.php /var/www/simplesamlphp/metadata +COPY config/simplesamlphp/saml20-idp-hosted.php /var/www/simplesamlphp/metadata +COPY config/simplesamlphp/phpinfo.php /var/www/simplesamlphp/www/admin/ + +# Apache +ARG ServerName +COPY saml.crt /etc/ssl/cert/saml.crt +COPY saml.key /etc/ssl/private/saml.key +COPY config/apache/ports.conf /etc/apache2 +COPY config/apache/simplesamlphp.conf /etc/apache2/sites-available +RUN echo "ServerName $ServerName" >> /etc/apache2/apache2.conf && \ + a2enmod ssl && \ + a2enmod rewrite &&\ + a2dissite 000-default.conf default-ssl.conf && \ + a2ensite simplesamlphp.conf +RUN sed -i '//,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf +RUN echo "RewriteEngine On" > /var/www/.htaccess +RUN echo "RewriteCond %{REQUEST_FILENAME} !-f" >> /var/www/.htaccess +RUN echo "RewriteCond %{REQUEST_FILENAME} !-d" >> /var/www/.htaccess +RUN echo "RewriteRule ^(.*)$ /simplesaml/admin/phpinfo.php [L,QSA]" >> /var/www/.htaccess +RUN echo "RedirectMatch ^/$ /simplesaml/" >> /var/www/.htaccess + +# Autorun memcached and apache +ENTRYPOINT service memcached start && apache2-foreground + +# Set work dir +WORKDIR /var/www/simplesamlphp + +# General setup +EXPOSE 8080 8443 diff --git a/test/idp/config/apache/ports.conf b/test/idp/config/apache/ports.conf new file mode 100644 index 0000000..7a782ee --- /dev/null +++ b/test/idp/config/apache/ports.conf @@ -0,0 +1,9 @@ +Listen 8080 + + + Listen 8443 + + + + Listen 8443 + diff --git a/test/idp/config/apache/simplesamlphp.conf b/test/idp/config/apache/simplesamlphp.conf new file mode 100644 index 0000000..13a5196 --- /dev/null +++ b/test/idp/config/apache/simplesamlphp.conf @@ -0,0 +1,23 @@ + + ServerName localhost + DocumentRoot /var/www/simplesamlphp + Alias /simplesaml /var/www/simplesamlphp/www + + + Require all granted + + + + + ServerName localhost + DocumentRoot /var/www/simplesamlphp + SSLEngine on + SSLCertificateFile /etc/ssl/cert/saml.crt + SSLCertificateKeyFile /etc/ssl/private/saml.key + Alias /simplesaml /var/www/simplesamlphp/www + + + Require all granted + + + diff --git a/test/idp/config/simplesamlphp/authsources.php b/test/idp/config/simplesamlphp/authsources.php new file mode 100644 index 0000000..1668812 --- /dev/null +++ b/test/idp/config/simplesamlphp/authsources.php @@ -0,0 +1,29 @@ + array( + 'core:AdminPassword', + ), + + 'example-userpass' => array( + 'exampleauth:UserPass', + 'aa:aa' => array( + 'uid' => array('1'), + 'memberOf' => array('group1, admins, students'), + 'email' => 'user1@route443.dev', + 'name' => 'Alan Alda', + 'telephoneNumber' => '+31(0)12345678', + 'company' => 'NGINX Inc.', + ), + 'bb:bb' => array( + 'uid' => array('2'), + 'memberOf' => array('group2, users, students'), + 'email' => 'user2@route443.dev', + 'name' => 'Ben Bernanke', + 'telephoneNumber' => '+31(0)12345678', + 'company' => 'NGINX Inc.', + ), + ), + +); diff --git a/test/idp/config/simplesamlphp/config.php b/test/idp/config/simplesamlphp/config.php new file mode 100644 index 0000000..7f28351 --- /dev/null +++ b/test/idp/config/simplesamlphp/config.php @@ -0,0 +1,851 @@ + 'simplesaml/', + 'certdir' => 'cert/', + 'loggingdir' => 'log/', + 'datadir' => 'data/', + + /* + * A directory where SimpleSAMLphp can save temporary files. + * + * SimpleSAMLphp will attempt to create this directory if it doesn't exist. + */ + 'tempdir' => '/tmp/simplesaml', + + + /* + * If you enable this option, SimpleSAMLphp will log all sent and received messages + * to the log file. + * + * This option also enables logging of the messages that are encrypted and decrypted. + * + * Note: The messages are logged with the DEBUG log level, so you also need to set + * the 'logging.level' option to LOG_DEBUG. + */ + 'debug' => true, + + /* + * When showerrors is enabled, all error messages and stack traces will be output + * to the browser. + * + * When errorreporting is enabled, a form will be presented for the user to report + * the error to technicalcontact_email. + */ + 'showerrors' => true, + 'errorreporting' => true, + + /** + * Custom error show function called from SimpleSAML_Error_Error::show. + * See docs/simplesamlphp-errorhandling.txt for function code example. + * + * Example: + * 'errors.show_function' => array('sspmod_example_Error_Show', 'show'), + */ + + /** + * This option allows you to enable validation of XML data against its + * schemas. A warning will be written to the log if validation fails. + */ + 'debug.validatexml' => false, + + /** + * This password must be kept secret, and modified from the default value 123. + * This password will give access to the installation page of SimpleSAMLphp with + * metadata listing and diagnostics pages. + * You can also put a hash here; run "bin/pwgen.php" to generate one. + */ + 'auth.adminpassword' => ((getenv('SIMPLESAMLPHP_ADMIN_PASSWORD') != '') ? getenv('SIMPLESAMLPHP_ADMIN_PASSWORD') : 'secret'), + 'admin.protectindexpage' => false, + 'admin.protectmetadata' => false, + + /** + * This is a secret salt used by SimpleSAMLphp when it needs to generate a secure hash + * of a value. It must be changed from its default value to a secret value. The value of + * 'secretsalt' can be any valid string of any length. + * + * A possible way to generate a random salt is by running the following command from a unix shell: + * tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' /dev/null;echo + */ + 'secretsalt' => ((getenv('SIMPLESAMLPHP_SECRET_SALT') != '') ? getenv('SIMPLESAMLPHP_SECRET_SALT') : 'defaultsecretsalt'), + + /* + * Some information about the technical persons running this installation. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. + */ + 'technicalcontact_name' => 'SuperAdmin', + 'technicalcontact_email' => 'superadmin@route443.dev', + + /* + * The timezone of the server. This option should be set to the timezone you want + * SimpleSAMLphp to report the time in. The default is to guess the timezone based + * on your system timezone. + * + * See this page for a list of valid timezones: http://php.net/manual/en/timezones.php + */ + 'timezone' => 'America/Los_Angeles', + + /* + * Logging. + * + * define the minimum log level to log + * SimpleSAML_Logger::ERR No statistics, only errors + * SimpleSAML_Logger::WARNING No statistics, only warnings/errors + * SimpleSAML_Logger::NOTICE Statistics and errors + * SimpleSAML_Logger::INFO Verbose logs + * SimpleSAML_Logger::DEBUG Full debug logs - not recommended for production + * + * Choose logging handler. + * + * Options: [syslog,file,errorlog] + * + */ + 'logging.level' => SimpleSAML_Logger::DEBUG, + 'logging.handler' => 'errorlog', + + /* + * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot + * control here how dates are displayed when using the syslog or errorlog handlers), but in general the options + * are: + * + * - %date{}: the date and time, with its format specified inside the brackets. See the PHP documentation + * of the strftime() function for more information on the format. If the brackets are omitted, the standard + * format is applied. This can be useful if you just want to control the placement of the date, but don't care + * about the format. + * + * - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname' + * option below. + * + * - %level: the log level (name or number depending on the handler used). + * + * - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind + * the trailing space). + * + * - %trackid: the track ID, an identifier that allows you to track a single session. + * + * - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the + * $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header. + * + * - %msg: the message to be logged. + * + */ + //'logging.format' => '%date{%b %d %H:%M:%S} %process %level %stat[%trackid] %msg', + + /* + * Choose which facility should be used when logging with syslog. + * + * These can be used for filtering the syslog output from SimpleSAMLphp into its + * own file by configuring the syslog daemon. + * + * See the documentation for openlog (http://php.net/manual/en/function.openlog.php) for available + * facilities. Note that only LOG_USER is valid on windows. + * + * The default is to use LOG_LOCAL5 if available, and fall back to LOG_USER if not. + */ + 'logging.facility' => defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER, + + /* + * The process name that should be used when logging to syslog. + * The value is also written out by the other logging handlers. + */ + 'logging.processname' => 'simplesamlphp', + + /* Logging: file - Logfilename in the loggingdir from above. + */ + 'logging.logfile' => 'simplesamlphp.log', + + /* (New) statistics output configuration. + * + * This is an array of outputs. Each output has at least a 'class' option, which + * selects the output. + */ + 'statistics.out' => array(// Log statistics to the normal log. + /* + array( + 'class' => 'core:Log', + 'level' => 'notice', + ), + */ + // Log statistics to files in a directory. One file per day. + /* + array( + 'class' => 'core:File', + 'directory' => '/var/log/stats', + ), + */ + ), + + + + /* + * Database + * + * This database configuration is optional. If you are not using + * core functionality or modules that require a database, you can + * skip this configuration. + */ + + /* + * Database connection string. + * Ensure that you have the required PDO database driver installed + * for your connection string. + */ + 'database.dsn' => 'mysql:host=localhost;dbname=saml', + + /* + * SQL database credentials + */ + 'database.username' => 'simplesamlphp', + 'database.password' => 'secret', + + /* + * (Optional) Table prefix + */ + 'database.prefix' => '', + + /* + * True or false if you would like a persistent database connection + */ + 'database.persistent' => false, + + /* + * Database slave configuration is optional as well. If you are only + * running a single database server, leave this blank. If you have + * a master/slave configuration, you can define as many slave servers + * as you want here. Slaves will be picked at random to be queried from. + * + * Configuration options in the slave array are exactly the same as the + * options for the master (shown above) with the exception of the table + * prefix. + */ + 'database.slaves' => array( + /* + array( + 'dsn' => 'mysql:host=myslave;dbname=saml', + 'username' => 'simplesamlphp', + 'password' => 'secret', + 'persistent' => false, + ), + */ + ), + + + + /* + * Enable + * + * Which functionality in SimpleSAMLphp do you want to enable. Normally you would enable only + * one of the functionalities below, but in some cases you could run multiple functionalities. + * In example when you are setting up a federation bridge. + */ + 'enable.saml20-idp' => true, + 'enable.shib13-idp' => false, + 'enable.adfs-idp' => false, + 'enable.wsfed-sp' => false, + 'enable.authmemcookie' => false, + + + /* + * Module enable configuration + * + * Configuration to override module enabling/disabling. + * + * Example: + * + * 'module.enable' => array( + * // Setting to TRUE enables. + * 'exampleauth' => TRUE, + * // Setting to FALSE disables. + * 'saml' => FALSE, + * // Unset or NULL uses default. + * 'core' => NULL, + * ), + * + */ + + + /* + * This value is the duration of the session in seconds. Make sure that the time duration of + * cookies both at the SP and the IdP exceeds this duration. + */ + 'session.duration' => 8 * (60 * 60), // 8 hours. + + /* + * Sets the duration, in seconds, data should be stored in the datastore. As the datastore is used for + * login and logout requests, thid option will control the maximum time these operations can take. + * The default is 4 hours (4*60*60) seconds, which should be more than enough for these operations. + */ + 'session.datastore.timeout' => (4 * 60 * 60), // 4 hours + + /* + * Sets the duration, in seconds, auth state should be stored. + */ + 'session.state.timeout' => (60 * 60), // 1 hour + + /* + * Option to override the default settings for the session cookie name + */ + 'session.cookie.name' => 'SimpleSAMLSessionIDIdp', + + /* + * Expiration time for the session cookie, in seconds. + * + * Defaults to 0, which means that the cookie expires when the browser is closed. + * + * Example: + * 'session.cookie.lifetime' => 30*60, + */ + 'session.cookie.lifetime' => 0, + + /* + * Limit the path of the cookies. + * + * Can be used to limit the path of the cookies to a specific subdirectory. + * + * Example: + * 'session.cookie.path' => '/simplesaml/', + */ + 'session.cookie.path' => '/', + + /* + * Cookie domain. + * + * Can be used to make the session cookie available to several domains. + * + * Example: + * 'session.cookie.domain' => '.example.org', + */ + 'session.cookie.domain' => null, + + /* + * Set the secure flag in the cookie. + * + * Set this to TRUE if the user only accesses your service + * through https. If the user can access the service through + * both http and https, this must be set to FALSE. + */ + 'session.cookie.secure' => false, + + /* + * Enable secure POST from HTTPS to HTTP. + * + * If you have some SP's on HTTP and IdP is normally on HTTPS, this option + * enables secure POSTing to HTTP endpoint without warning from browser. + * + * For this to work, module.php/core/postredirect.php must be accessible + * also via HTTP on IdP, e.g. if your IdP is on + * https://idp.example.org/ssp/, then + * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. + */ + 'enable.http_post' => false, + + /* + * Options to override the default settings for php sessions. + */ + 'session.phpsession.cookiename' => 'PHPSESSIDIDP', + 'session.phpsession.savepath' => null, + 'session.phpsession.httponly' => true, + + /* + * Option to override the default settings for the auth token cookie + */ + 'session.authtoken.cookiename' => 'SimpleSAMLAuthTokenIdp', + + /* + * Options for remember me feature for IdP sessions. Remember me feature + * has to be also implemented in authentication source used. + * + * Option 'session.cookie.lifetime' should be set to zero (0), i.e. cookie + * expires on browser session if remember me is not checked. + * + * Session duration ('session.duration' option) should be set according to + * 'session.rememberme.lifetime' option. + * + * It's advised to use remember me feature with session checking function + * defined with 'session.check_function' option. + */ + 'session.rememberme.enable' => false, + 'session.rememberme.checked' => false, + 'session.rememberme.lifetime' => (14 * 86400), + + /** + * Custom function for session checking called on session init and loading. + * See docs/simplesamlphp-advancedfeatures.txt for function code example. + * + * Example: + * 'session.check_function' => array('sspmod_example_Util', 'checkSession'), + */ + + /* + * Languages available, RTL languages, and what language is default + */ + 'language.available' => array( + 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'fr', 'it', 'nl', 'lb', 'cs', + 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', 'ru', 'et', + 'he', 'id', 'sr', 'lv', 'ro', 'eu' + ), + 'language.rtl' => array('ar', 'dv', 'fa', 'ur', 'he'), + 'language.default' => 'en', + + /* + * Options to override the default settings for the language parameter + */ + 'language.parameter.name' => 'language', + 'language.parameter.setcookie' => true, + + /* + * Options to override the default settings for the language cookie + */ + 'language.cookie.name' => 'language', + 'language.cookie.domain' => null, + 'language.cookie.path' => '/', + 'language.cookie.lifetime' => (60 * 60 * 24 * 900), + + /** + * Custom getLanguage function called from SimpleSAML_XHTML_Template::getLanguage(). + * Function should return language code of one of the available languages or NULL. + * See SimpleSAML_XHTML_Template::getLanguage() source code for more info. + * + * This option can be used to implement a custom function for determining + * the default language for the user. + * + * Example: + * 'language.get_language_function' => array('sspmod_example_Template', 'getLanguage'), + */ + + /* + * Extra dictionary for attribute names. + * This can be used to define local attributes. + * + * The format of the parameter is a string with :. + * + * Specifying this option will cause us to look for modules//dictionaries/.definition.json + * The dictionary should look something like: + * + * { + * "firstattribute": { + * "en": "English name", + * "no": "Norwegian name" + * }, + * "secondattribute": { + * "en": "English name", + * "no": "Norwegian name" + * } + * } + * + * Note that all attribute names in the dictionary must in lowercase. + * + * Example: 'attributes.extradictionary' => 'ourmodule:ourattributes', + */ + 'attributes.extradictionary' => null, + + /* + * Which theme directory should be used? + */ + 'theme.use' => 'default', + + + /* + * Default IdP for WS-Fed. + */ + 'default-wsfed-idp' => 'urn:federation:pingfederate:localhost', + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => true, + 'idpdisco.rememberchecked' => true, + + // Disco service only accepts entities it knows. + 'idpdisco.validate' => true, + + 'idpdisco.extDiscoveryStorage' => null, + + /* + * IdP Discovery service look configuration. + * Wether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box + * gives the best use experience. + * + * When using dropdown box a cookie is used to highlight the previously chosen IdP in the dropdown. + * This makes it easier for the user to choose the IdP + * + * Options: [links,dropdown] + * + */ + 'idpdisco.layout' => 'dropdown', + + /* + * Whether SimpleSAMLphp should sign the response or the assertion in SAML 1.1 authentication + * responses. + * + * The default is to sign the assertion element, but that can be overridden by setting this + * option to TRUE. It can also be overridden on a pr. SP basis by adding an option with the + * same name to the metadata of the SP. + */ + 'shib13.signresponse' => true, + + + /* + * Authentication processing filters that will be executed for all IdPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.idp' => array( + /* Enable the authproc filter below to add URN Prefixces to all attributes + 10 => array( + 'class' => 'core:AttributeMap', 'addurnprefix' + ), */ + /* Enable the authproc filter below to automatically generated eduPersonTargetedID. + 20 => 'core:TargetedID', + */ + + // Adopts language from attribute to use in UI + 30 => 'core:LanguageAdaptor', + + /* Add a realm attribute from edupersonprincipalname + 40 => 'core:AttributeRealm', + */ + 45 => array( + 'class' => 'core:StatisticsWithAttribute', + 'attributename' => 'realm', + 'type' => 'saml20-idp-SSO', + ), + + /* When called without parameters, it will fallback to filter attributes ‹the old way› + * by checking the 'attributes' parameter in metadata on IdP hosted and SP remote. + */ + 50 => 'core:AttributeLimit', + + /* + * Search attribute "distinguishedName" for pattern and replaces if found + + 60 => array( + 'class' => 'core:AttributeAlter', + 'pattern' => '/OU=studerende/', + 'replacement' => 'Student', + 'subject' => 'distinguishedName', + '%replace', + ), + */ + + /* + * Consent module is enabled (with no permanent storage, using cookies). + + 90 => array( + 'class' => 'consent:Consent', + 'store' => 'consent:Cookie', + 'focus' => 'yes', + 'checked' => TRUE + ), + */ + // If language is set in Consent module it will be added as an attribute. + 99 => 'core:LanguageAdaptor', + ), + /* + * Authentication processing filters that will be executed for all SPs + * Both Shibboleth and SAML 2.0 + */ + 'authproc.sp' => array( + /* + 10 => array( + 'class' => 'core:AttributeMap', 'removeurnprefix' + ), + */ + + /* + * Generate the 'group' attribute populated from other variables, including eduPersonAffiliation. + 60 => array( + 'class' => 'core:GenerateGroups', 'eduPersonAffiliation' + ), + */ + /* + * All users will be members of 'users' and 'members' + 61 => array( + 'class' => 'core:AttributeAdd', 'groups' => array('users', 'members') + ), + */ + + // Adopts language from attribute to use in UI + 90 => 'core:LanguageAdaptor', + + ), + + + /* + * This option configures the metadata sources. The metadata sources is given as an array with + * different metadata sources. When searching for metadata, simpleSAMPphp will search through + * the array from start to end. + * + * Each element in the array is an associative array which configures the metadata source. + * The type of the metadata source is given by the 'type' element. For each type we have + * different configuration options. + * + * Flat file metadata handler: + * - 'type': This is always 'flatfile'. + * - 'directory': The directory we will load the metadata files from. The default value for + * this option is the value of the 'metadatadir' configuration option, or + * 'metadata/' if that option is unset. + * + * XML metadata handler: + * This metadata handler parses an XML file with either an EntityDescriptor element or an + * EntitiesDescriptor element. The XML file may be stored locally, or (for debugging) on a remote + * web server. + * The XML hetadata handler defines the following options: + * - 'type': This is always 'xml'. + * - 'file': Path to the XML file with the metadata. + * - 'url': The URL to fetch metadata from. THIS IS ONLY FOR DEBUGGING - THERE IS NO CACHING OF THE RESPONSE. + * + * MDX metadata handler: + * This metadata handler looks up for the metadata of an entity at the given MDX server. + * The MDX metadata handler defines the following options: + * - 'type': This is always 'mdx'. + * - 'server': URL of the MDX server (url:port). Mandatory. + * - 'validateFingerprint': The fingerprint of the certificate used to sign the metadata. + * You don't need this option if you don't want to validate the signature on the metadata. Optional. + * - 'cachedir': Directory where metadata can be cached. Optional. + * - 'cachelength': Maximum time metadata cah be cached, in seconds. Default to 24 + * hours (86400 seconds). Optional. + * + * PDO metadata handler: + * This metadata handler looks up metadata of an entity stored in a database. + * + * Note: If you are using the PDO metadata handler, you must configure the database + * options in this configuration file. + * + * The PDO metadata handler defines the following options: + * - 'type': This is always 'pdo'. + * + * + * Examples: + * + * This example defines two flatfile sources. One is the default metadata directory, the other + * is a metadata directory with autogenerated metadata files. + * + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'flatfile', 'directory' => 'metadata-generated'), + * ), + * + * This example defines a flatfile source and an XML source. + * 'metadata.sources' => array( + * array('type' => 'flatfile'), + * array('type' => 'xml', 'file' => 'idp.example.org-idpMeta.xml'), + * ), + * + * This example defines an mdx source. + * 'metadata.sources' => array( + * array('type' => 'mdx', server => 'http://mdx.server.com:8080', 'cachedir' => '/var/simplesamlphp/mdx-cache', 'cachelength' => 86400) + * ), + * + * This example defines an pdo source. + * 'metadata.sources' => array( + * array('type' => 'pdo') + * ), + * + * Default: + * 'metadata.sources' => array( + * array('type' => 'flatfile') + * ), + */ + 'metadata.sources' => array( + array('type' => 'flatfile'), + ), + + + /* + * Configure the datastore for SimpleSAMLphp. + * + * - 'phpsession': Limited datastore, which uses the PHP session. + * - 'memcache': Key-value datastore, based on memcache. + * - 'sql': SQL datastore, using PDO. + * + * The default datastore is 'phpsession'. + * + * (This option replaces the old 'session.handler'-option.) + */ + 'store.type' => 'memcache', + + + /* + * The DSN the sql datastore should connect to. + * + * See http://www.php.net/manual/en/pdo.drivers.php for the various + * syntaxes. + */ + 'store.sql.dsn' => 'sqlite:/path/to/sqlitedatabase.sq3', + + /* + * The username and password to use when connecting to the database. + */ + 'store.sql.username' => null, + 'store.sql.password' => null, + + /* + * The prefix we should use on our tables. + */ + 'store.sql.prefix' => 'SimpleSAMLphp', + + + /* + * Configuration for the 'memcache' session store. This allows you to store + * multiple redundant copies of sessions on different memcache servers. + * + * 'memcache_store.servers' is an array of server groups. Every data + * item will be mirrored in every server group. + * + * Each server group is an array of servers. The data items will be + * load-balanced between all servers in each server group. + * + * Each server is an array of parameters for the server. The following + * options are available: + * - 'hostname': This is the hostname or ip address where the + * memcache server runs. This is the only required option. + * - 'port': This is the port number of the memcache server. If this + * option isn't set, then we will use the 'memcache.default_port' + * ini setting. This is 11211 by default. + * - 'weight': This sets the weight of this server in this server + * group. http://php.net/manual/en/function.Memcache-addServer.php + * contains more information about the weight option. + * - 'timeout': The timeout for this server. By default, the timeout + * is 3 seconds. + * + * Example of redundant configuration with load balancing: + * This configuration makes it possible to lose both servers in the + * a-group or both servers in the b-group without losing any sessions. + * Note that sessions will be lost if one server is lost from both the + * a-group and the b-group. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'mc_a1'), + * array('hostname' => 'mc_a2'), + * ), + * array( + * array('hostname' => 'mc_b1'), + * array('hostname' => 'mc_b2'), + * ), + * ), + * + * Example of simple configuration with only one memcache server, + * running on the same computer as the web server: + * Note that all sessions will be lost if the memcache server crashes. + * + * 'memcache_store.servers' => array( + * array( + * array('hostname' => 'localhost'), + * ), + * ), + * + */ + 'memcache_store.servers' => array( + array( + array('hostname' => 'localhost'), + ), + ), + + + /* + * This value allows you to set a prefix for memcache-keys. The default + * for this value is 'SimpleSAMLphp', which is fine in most cases. + * + * When running multiple instances of SSP on the same host, and more + * than one instance is using memcache, you probably want to assign + * a unique value per instance to this setting to avoid data collision. + */ + 'memcache_store.prefix' => 'simpleSAMLphp', + + + /* + * This value is the duration data should be stored in memcache. Data + * will be dropped from the memcache servers when this time expires. + * The time will be reset every time the data is written to the + * memcache servers. + * + * This value should always be larger than the 'session.duration' + * option. Not doing this may result in the session being deleted from + * the memcache servers while it is still in use. + * + * Set this value to 0 if you don't want data to expire. + * + * Note: The oldest data will always be deleted if the memcache server + * runs out of storage space. + */ + 'memcache_store.expires' => 36 * (60 * 60), // 36 hours. + + + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => false, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => null, + 'metadata.sign.privatekey_pass' => null, + 'metadata.sign.certificate' => null, + + + /* + * Proxy to use for retrieving URLs. + * + * Example: + * 'proxy' => 'tcp://proxy.example.com:5100' + */ + 'proxy' => null, + + /* + * Array of domains that are allowed when generating links or redirections + * to URLs. SimpleSAMLphp will use this option to determine whether to + * to consider a given URL valid or not, but you should always validate + * URLs obtained from the input on your own (i.e. ReturnTo or RelayState + * parameters obtained from the $_REQUEST array). + * + * SimpleSAMLphp will automatically add your own domain (either by checking + * it dynamically, or by using the domain defined in the 'baseurlpath' + * directive, the latter having precedence) to the list of trusted domains, + * in case this option is NOT set to NULL. In that case, you are explicitly + * telling SimpleSAMLphp to verify URLs. + * + * Set to an empty array to disallow ALL redirections or links pointing to + * an external URL other than your own domain. This is the default behaviour. + * + * Set to NULL to disable checking of URLs. DO NOT DO THIS UNLESS YOU KNOW + * WHAT YOU ARE DOING! + * + * Example: + * 'trusted.url.domains' => array('sp.example.com', 'app.example.com'), + */ + 'trusted.url.domains' => array(), + +); diff --git a/test/idp/config/simplesamlphp/phpinfo.php b/test/idp/config/simplesamlphp/phpinfo.php new file mode 100644 index 0000000..41ed4c2 --- /dev/null +++ b/test/idp/config/simplesamlphp/phpinfo.php @@ -0,0 +1,8 @@ + '__DEFAULT__', + + // X.509 key and certificate. Relative to the cert directory. + 'privatekey' => 'saml.key', + 'certificate' => 'saml.crt', + + /* + * Authentication source to use. Must be one that is configured in + * 'config/authsources.php'. + */ + 'auth' => 'example-userpass', + 'simplesaml.nameidattribute' => 'email', + + 'OrganizationName' => [ + 'en' => 'NGINX SAML', + 'ru' => 'энджин-икс самл', + ], + 'OrganizationURL' => [ + 'en' => 'https://example.org', + 'ru' => 'https://example.ru', + ], + /* Uncomment the following to use the uri NameFormat on attributes. */ + /* + 'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', + 'authproc' => [ + // Convert LDAP names to oids. + 100 => ['class' => 'core:AttributeMap', 'name2oid'], + ], + */ + + /* + * Uncomment the following to specify the registration information in the + * exported metadata. Refer to: + * http://docs.oasis-open.org/security/saml/Post2.0/saml-metadata-rpi/v1.0/cs01/saml-metadata-rpi-v1.0-cs01.html + * for more information. + */ + /* + 'RegistrationInfo' => [ + 'authority' => 'urn:mace:example.org', + 'instant' => '2008-01-17T11:28:03Z', + 'policies' => [ + 'en' => 'http://example.org/policy', + 'es' => 'http://example.org/politica', + ], + ], + */ +]; diff --git a/test/idp/config/simplesamlphp/saml20-sp-remote.php b/test/idp/config/simplesamlphp/saml20-sp-remote.php new file mode 100644 index 0000000..8bde6b3 --- /dev/null +++ b/test/idp/config/simplesamlphp/saml20-sp-remote.php @@ -0,0 +1,51 @@ + getenv('SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE'), + 'SingleLogoutService' => getenv('SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE'), +); + +$metadata['http://sp.route443.dev'] = array ( + 'entityid' => 'http://sp.route443.dev', + 'contacts' => + array ( + ), + 'metadata-set' => 'saml20-sp-remote', + 'AssertionConsumerService' => + array ( + 0 => + array ( + 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'Location' => 'http://sp.route443.dev/saml/acs', + 'index' => 0, + 'isDefault' => true, + ), + ), + 'SingleLogoutService' => + array ( + 0 => + array ( + 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'Location' => 'http://sp.route443.dev/saml/sls', + 'ResponseLocation' => 'http://sp.route443.dev/saml/sls', + ), + ), + 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', + 'certificate' => 'saml.crt', + 'signature.privatekey' => 'saml.key', + 'SingleSignOnServiceBinding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'SingleLogoutServiceBinding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + 'saml20.sign.response' => TRUE, + 'saml20.sign.assertion' => TRUE, + 'sign.logout' => TRUE, + 'redirect.sign' => TRUE, + 'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + 'validate.authnrequest' => TRUE, + 'validate.logout' => TRUE, + 'assertion.encryption' => FALSE, + 'nameid.encryption' => FALSE, +); \ No newline at end of file diff --git a/test/idp/docker-compose.yml b/test/idp/docker-compose.yml new file mode 100644 index 0000000..9ed9201 --- /dev/null +++ b/test/idp/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + testsamlidp_idp: + build: + context: . + dockerfile: ./Dockerfile + args: + - ServerName=idp.route443.dev + environment: + SIMPLESAMLPHP_SP_ENTITY_ID: https://idp.route443.dev + SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE: https://idp.route443.dev/simplesaml/module.php/saml/sp/saml2-acs.php/test-sp + SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE: https://idp.route443.dev/simplesaml/module.php/saml/sp/saml2-logout.php/test-sp + SIMPLESAMLPHP_ADMIN_PASSWORD: admin + SIMPLESAMLPHP_SECRET_SALT: salt + ports: + - "8080:8080" + - "8443:8443" + tty: true diff --git a/test/makefile b/test/makefile new file mode 100644 index 0000000..7329560 --- /dev/null +++ b/test/makefile @@ -0,0 +1,120 @@ +# makefile to prepare SAML test env for nginx +WORKDIR=${CURDIR} + +# build directory +SESRC:=~/nginx-se +NGX:=$(WORKDIR)/nginx-se +NJS:=$(WORKDIR)/njs +IDP:=$(WORKDIR)/idp + +# nginx installation prefix +PREFIX:=$(WORKDIR)/nginx + +# SAML conf files to copy +CFLIST:=saml_sp.js frontend.conf saml_sp_configuration.conf saml_sp.server_conf +CRT:=saml.crt saml.key saml.spki + +# this is needed to instrument code properly +PROF_CFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC +PROF_LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now + +# used nginx modules +MODULES= --with-http_ssl_module \ + --with-http_addition_module \ + --with-http_sub_module \ + --with-http_gzip_static_module \ + --with-http_auth_request_module \ + --with-http_gunzip_module \ + --with-http_stub_status_module \ + --with-http_session_log_module \ + --with-stream \ + --with-stream_ssl_module \ + --with-stream_ssl_preread_module \ + --with-file-aio \ + --with-mail_ssl_module \ + --with-http_v2_module \ + --with-http_slice_module \ + --with-http_auth_jwt_module \ + --with-debug \ + --with-threads \ + --with-compat \ + --add-module=$(NJS)/nginx + +all: build start + @echo "Done!" + +# not really targets, just shortcuts +.PHONY: build start stop restart clean + +# builds NGINX Plus and NJS +build: + cp -r $(SESRC) $(WORKDIR) || { echo "nginx-se not found!"; exit 1; } + hg clone http://hg.nginx.org/njs + cd $(NJS) && \ + ./configure && make + cd $(NGX) && \ + hg up se && \ + ./auto/configure --with-cc-opt="$(PROF_CFLAGS)" \ + --with-ld-opt="$(PROF_LDFLAGS)" \ + --prefix=$(PREFIX) $(MODULES) && \ + make -j$(nproc) install && \ + rm -f $(NGX)/autotest.* + @rm -rf $(NGX) + @rm -rf $(NJS) + cd $(IDP) &&\ + printf "[ req ]\ndefault_bits = 4096\nencrypt_key = no\ndistinguished_name = req_distinguished_name\n[ req_distinguished_name ]" > openssl.conf &&\ + for name in saml; do \ + openssl req -x509 -new \ + -config openssl.conf -subj "/CN=$$name/" \ + -out $$name.crt -keyout $$name.key \ + >> openssl.out 2>&1 || exit 1; \ + openssl x509 \ + -in $$name.crt -outform DER \ + -out $$name.der \ + >> openssl.out 2>&1 || exit 1; \ + openssl x509 -inform DER \ + -in $$name.der -pubkey -noout \ + > $$name.spki 2>&1 || exit 1; \ + done &&\ + chmod -R 664 saml.* &&\ + mkdir $(PREFIX)/conf/conf.d &&\ + $(foreach f,$(CRT),cp $(f) $(PREFIX)/conf/conf.d/;) + cd $(PREFIX) &&\ + chmod -R 777 conf &&\ + chmod -R 777 logs &&\ + sed -i 's/listen\s*80;/listen 8011;/g' conf/nginx.conf &&\ + sed -i 's/#gzip\s*on;/include conf.d\/*.conf;/g' conf/nginx.conf &&\ + ln -s conf/conf.d conf.d + @echo "NGINX and NJS BUILD OK" + +# start nginx with saml configuration and SimpleSAMLphp docker container +start: + cd $(IDP) &&\ + docker-compose up -d --build + cd $(WORKDIR) &&\ + $(foreach f,$(CFLIST),cp ../$(f) $(PREFIX)/conf.d/;) + cd $(PREFIX) &&\ + sudo sbin/nginx -p $(PREFIX) + +# start nginx with saml configuration and SimpleSAMLphp docker container +restart: + cd $(WORKDIR) &&\ + $(foreach f,$(CFLIST),cp ../$(f) $(PREFIX)/conf.d/;) + cd $(PREFIX) &&\ + sudo sbin/nginx -s reload + +# stop nginx with saml configuration and SimpleSAMLphp docker container +stop: + cd $(IDP) &&\ + docker-compose down + cd $(PREFIX) &&\ + sudo sbin/nginx -s quit + +# cleanup +clean: + @rm -rf $(PREFIX) + @rm -rf $(NGX) + @rm -rf $(NJS) + @rm -rf $(IDP)/saml.* + @rm -rf $(IDP)/openssl.* + @echo "CLEANUP DONE" \ No newline at end of file