diff --git a/.envrc.sample b/.envrc.sample index a9fe2d6..a028ac9 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -8,15 +8,26 @@ export MEDIAWIKI_SITENAMESPACE="Example_Wiki" export MEDIAWIKI_EMAIL_CONTACT="info@wiki.example.com" export MEDIAWIKI_EMAIL_FROM="wiki@wiki.example.com" -export SAML_CONTACT_NAME="Sam Ell" -export SAML_CONTACT_EMAIL="saml@example.com" - export DB_ROOT_PASSWORD_VERSION=v1 export DB_PASSWORD_VERSION=v1 export MEDIAWIKI_SECRET_KEY_VERSION=v1 -export SAML_ADMIN_PASSWORD_VERSION=v1 export LOCAL_SETTINGS_CONF_VERSION=v1 export HTACCESS_CONF_VERSION=v1 export ENTRYPOINT_CONF_VERSION=v1 + +# SAML + +export SAML_ENABLED=1 +export SAML_CONTACT_NAME="Sam Ell" +export SAML_CONTACT_EMAIL="saml@example.com" + +export SAML_EMAIL_ATTRIBUTE=email +export SAML_REAL_NAME_ATTRIBUTE=realname +export SAML_AUTH_SOURCE_ID=dev-sp +export SAML_USERNAME_ATTRIBUTE=user + +export SAML_ADMIN_PASSWORD_VERSION=v1 +export SAML_SECRET_SALT_VERSION=v1 + export SAML_ENTRYPOINT_CONF_VERSION=v1 diff --git a/LocalSettings.php.tmpl b/LocalSettings.php.tmpl index 074b94f..ad00a0f 100644 --- a/LocalSettings.php.tmpl +++ b/LocalSettings.php.tmpl @@ -132,16 +132,11 @@ wfLoadSkin( 'MonoBook' ); wfLoadSkin( 'Timeless' ); wfLoadSkin( 'Vector' ); - -# Enabled extensions. Most of the extensions are enabled by adding -# wfLoadExtensions('ExtensionName'); -# to LocalSettings.php. Check specific extension documentation for more details. -# The following extensions were automatically enabled: -wfLoadExtension( 'VisualEditor' ); - # End of automatically generated settings. # Add more configuration options below. +wfLoadExtension( 'VisualEditor' ); + $wgDefaultUserOptions['visualeditor-enable'] = 1; $wgVisualEditorAllowLossySwitching = false; @@ -162,3 +157,20 @@ $wgVirtualRestConfig['modules']['parsoid'] = [ // whether to parse URL as if they were meant for RESTBase (boolean or null, optional) 'restbaseCompat' => null, ]; + +{{ if eq (env "SAML_ENABLED") "1" }} +wfLoadExtension( 'PluggableAuth' ); + +wfLoadExtension( 'SimpleSAMLphp' ); + +$wgSimpleSAMLphp_InstallDir = "/var/simplesamlphp/"; +$wgSimpleSAMLphp_AuthSourceId = "{{ env "SAML_SERVICE_PROVIDER" }}"; +$wgSimpleSAMLphp_RealNameAttribute = "{{ env "SAML_REAL_NAME_ATTRIBUTE" }}"; +$wgSimpleSAMLphp_EmailAttribute = "{{ env "SAML_EMAIL_ATTRIBUTE" }}"; +$wgSimpleSAMLphp_UsernameAttribute = "{{ env "SAML_USERNAME_ATTRIBUTE" }}"; + +$wgGroupPermissions['*']['autocreateaccount'] = true; +$wgGroupPermissions['*']['createaccount'] = false; + +$wgDebugLogFile = "/var/log/debug-{$wgDBname}.log"; +{{ end }} diff --git a/README.md b/README.md index 1694cce..dbdca72 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,15 @@ Based on [`mediawiki-ve-bundle`][mediawiki-ve]. your Docker swarm box 4. `direnv allow` (or `. .envrc`) 5. `abra secret_generate db_password v1 && abra secret_generate db_root_password v2` -7. `abra secret_generate mediawiki_secret_key "pwgen -n 64 1"` -8. `abra deploy` -9. `abra service_run mediawiki /bin/bash` to open a shell -10. `php /var/www/html/maintenance/createAndPromote.php YourUsername YourPassword` +6. `abra secret_generate mediawiki_secret_key "pwgen -n 64 1"` +7. `abra deploy` +8. `abra service_run mediawiki /bin/bash` to open a shell +9. `php /var/www/html/maintenance/createAndPromote.php YourUsername YourPassword` + +## SimpleSAML + +1. `abra secret_generate saml_admin_password v1` +2. `abra secret_generate saml_secret_key v1 "pwgen -n 64 1"` ## License diff --git a/compose.yml b/compose.yml index 1bd8c73..7a34e47 100644 --- a/compose.yml +++ b/compose.yml @@ -22,6 +22,7 @@ services: delay: "60s" max_attempts: 3 window: 120s + mediawiki: image: 'revianlabs/mediawiki-ve-bundle' environment: @@ -31,10 +32,17 @@ services: - MEDIAWIKI_EMAIL_FROM=${MEDIAWIKI_EMAIL_FROM} - MEDIAWIKI_SITENAME=${MEDIAWIKI_SITENAME} - MEDIAWIKI_SITENAMESPACE=${MEDIAWIKI_SITENAMESPACE} + - SAML_ENABLED=${SAML_ENABLED} + - SAML_AUTH_SOURCE_ID=${SAML_AUTH_SOURCE_ID} + - SAML_EMAIL_ATTRIBUTE=${SAML_EMAIL_ATTRIBUTE} + - SAML_REAL_NAME_ATTRIBUTE=${SAML_REAL_NAME_ATTRIBUTE} + - SAML_SERVICE_PROVIDER=${SAML_SERVICE_PROVIDER} + - SAML_USERNAME_ATTRIBUTE=${SAML_USERNAME_ATTRIBUTE} volumes: - 'mediawiki_images:/var/www/html/images' - 'parsoid:/usr/lib/parsoid' - - 'simplesaml:/var/www/html/simplesamlphp' + - 'simplesaml:/var/simplesamlphp/' + - 'simplesaml_log:/var/simplesamlphp/log' configs: - source: LocalSettings_conf target: /var/www/html/LocalSettings.php @@ -66,10 +74,12 @@ services: image: venatorfox/simplesamlphp:latest secrets: - saml_admin_password + - saml_secret_salt environment: + - DOMAIN=${DOMAIN} - CONFIG_BASEURLPATH=https://${DOMAIN}/simplesaml/ - CONFIG_AUTHADMINPASSWORD_FILE=/run/secrets/saml_admin_password - - CONFIG_SECRETSALT=exampleabcdefghijklmnopqrstuvwxy + - CONFIG_SECRETSALT_FILE=/run/secrets/saml_secret_salt - CONFIG_TECHNICALCONTACT_NAME=${SAML_CONTACT_NAME} - CONFIG_TECHNICALCONTACT_EMAIL=${SAML_CONTACT_EMAIL} - CONFIG_SHOWERRORS=true @@ -77,15 +87,15 @@ services: - CONFIG_ADMINPROTECTINDEXPAGE=true - CONFIG_LOGGINGLEVEL=INFO - CONFIG_ENABLESAML20IDP=true - #- CONFIG_STORETYPE=memcache + - CONFIG_STORETYPE=sql #- CONFIG_MEMCACHESTOREPREFIX=simplesamlphp - #- CONFIG_MEMCACHESTORESERVERS= 'memcache_store.servers' => [\n [\n ['hostname' => 'some-memcacheda01'],\n ['hostname' => 'some-memcacheda02'],\n ],\n [\n ['hostname' => 'some-memcachedb01'],\n ['hostname' => 'some-memcachedb02'],\n ], + #- CONFIG_MEMCACHESTORESERVERS= 'memcache_store.servers' => [\n [\n ['hostname' => 'memcached']\n ], - OPENLDAP_TLS_REQCERT=allow - MTA_NULLCLIENT=true - POSTFIX_MYHOSTNAME=${DOMAIN} - POSTFIX_MYORIGIN=$$mydomain - POSTFIX_INETINTERFACES=loopback-only - - DOCKER_REDIRECTLOGS=true + - DOCKER_REDIRECTLOGS=false tty: true configs: - source: entrypoint_saml_conf @@ -93,10 +103,12 @@ services: mode: 0555 volumes: - simplesaml:/var/simplesamlphp/ + - simplesaml_log:/var/simplesamlphp/log networks: - internal - proxy entrypoint: /docker-entrypoint.simplesaml.sh + #entrypoint: ["tail", "-f", "/dev/null"] deploy: labels: - "traefik.enable=true" @@ -105,11 +117,18 @@ services: - "traefik.http.routers.${STACK_NAME}_simplesaml.entrypoints=web-secure" - "traefik.http.routers.${STACK_NAME}_simplesaml.tls.certresolver=${LETS_ENCRYPT_ENV}" + memcached: + image: memcached:latest + command: "docker-entrypoint.sh memcached" + networks: + - internal + volumes: mariadb: mediawiki_images: parsoid: simplesaml: + simplesaml_log: networks: proxy: @@ -127,7 +146,10 @@ secrets: name: ${STACK_NAME}_mediawiki_secret_key_${MEDIAWIKI_SECRET_KEY_VERSION} external: true saml_admin_password: - name: ${STACK_NAME}_saml_admin_password_${MEDIAWIKI_SECRET_KEY_VERSION} + name: ${STACK_NAME}_saml_admin_password_${SAML_ADMIN_PASSWORD_VERSION} + external: true + saml_secret_salt: + name: ${STACK_NAME}_saml_secret_salt_${SAML_SECRET_SALT_VERSION} external: true configs: @@ -144,6 +166,6 @@ configs: file: entrypoint.sh.tmpl template_driver: golang entrypoint_saml_conf: - name: ${STACK_NAME}_entrypoint_saml_${ENTRYPOINT_CONF_VERSION} + name: ${STACK_NAME}_entrypoint_saml_${SAML_ENTRYPOINT_CONF_VERSION} file: entrypoint.simplesaml.sh.tmpl template_driver: golang diff --git a/entrypoint.sh.tmpl b/entrypoint.sh.tmpl index 0845c31..005fe48 100755 --- a/entrypoint.sh.tmpl +++ b/entrypoint.sh.tmpl @@ -3,26 +3,44 @@ set -eu -o pipefail init_db() { - set -eu + set -eu + if ! type mysql > /dev/null 2>&1; then apt update && apt install -y mariadb-client + fi - PASSWORD=`cat /run/secrets/db_password` - TABLE_COUNT=$(mysql -u mediawiki --password="$PASSWORD" -h mariadb mediawiki -e "SELECT count(*) AS TOTAL FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'mediawiki';" -N -B) + PASSWORD=`cat /run/secrets/db_password` + TABLE_COUNT=$(mysql -u mediawiki --password="$PASSWORD" -h mariadb mediawiki -e "SELECT count(*) AS TOTAL FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'mediawiki';" -N -B) - if [[ "${TABLE_COUNT}" == "0" ]]; then - mysql -u mediawiki --password="$PASSWORD" -h mariadb mediawiki < /var/www/html/maintenance/tables.sql - else - php /var/www/html/maintenance/update.php - fi + if [[ "${TABLE_COUNT}" == "0" ]]; then + mysql -u mediawiki --password="$PASSWORD" -h mariadb mediawiki < /var/www/html/maintenance/tables.sql + else + php /var/www/html/maintenance/update.php + fi +} + +install_extensions() { + if [ ! -d /var/www/html/extensions/PluggableAuth ]; then + git clone --depth 1 -b REL1_32 \ + https://gerrit.wikimedia.org/r/p/mediawiki/extensions/PluggableAuth \ + /var/www/html/extensions/PluggableAuth + fi + + if [ ! -d /var/www/html/extensions/SimpleSAMLphp ]; then + git clone --depth 1 -b REL1_32 \ + https://gerrit.wikimedia.org/r/p/mediawiki/extensions/SimpleSAMLphp \ + /var/www/html/extensions/SimpleSAMLphp + fi } main() { - set -eu + set -eu - a2enmod rewrite + a2enmod rewrite - init_db + install_extensions + + init_db } main diff --git a/entrypoint.simplesaml.sh.tmpl b/entrypoint.simplesaml.sh.tmpl index 1cd285f..c1c8542 100644 --- a/entrypoint.simplesaml.sh.tmpl +++ b/entrypoint.simplesaml.sh.tmpl @@ -21,6 +21,43 @@ file_env() { unset "$fileVar" } -file_env "CONFIG_AUTHADMINPASSWORD" +load_vars() { + file_env "CONFIG_AUTHADMINPASSWORD" + file_env "CONFIG_SECRETSALT" +} + +generate_certs() { + CERT_DIR=/var/simplesamlphp/cert + + if [ -f "$CERT_DIR/saml.crt" ] && [ -f "$CERT_DIR/saml.pem" ]; then + return + fi + + if ! type openssl > /dev/null 2>&1; then + yum install -q -y openssl + fi + + openssl req -newkey rsa:4096 -new -x509 \ + -days 3652 -nodes \ + -out "$CERT_DIR/saml.crt" \ + -keyout "$CERT_DIR/saml.pem" \ + -subj "/C=XX/ST=/L=/O=/OU=SimpleSAML/CN=${DOMAIN}" +} + +enable_plugins() { + touch /var/simplesamlphp/modules/cas/enable +} + +main() { + set -eu + + load_vars + + enable_plugins + + generate_certs +} + +main /init "$@" diff --git a/metadata/saml20-idp-hosted.php b/metadata/saml20-idp-hosted.php deleted file mode 100644 index 51da8ec..0000000 --- a/metadata/saml20-idp-hosted.php +++ /dev/null @@ -1,93 +0,0 @@ - 'wisera.auth.dev.iww.org.uk', - - // X.509 key and certificate. Relative to the cert directory. - 'privatekey' => 'saml.pem', - 'certificate' => 'saml.crt', - - /* - * Authentication source to use. Must be one that is configured in - * 'config/authsources.php'. - */ - 'auth' => 'live', - - /* - * WARNING: SHA-1 is disallowed starting January the 1st, 2014. - * - * Uncomment the following option to start using SHA-256 for your signatures. - * Currently, SimpleSAMLphp defaults to SHA-1, which has been deprecated since - * 2011, and will be disallowed by NIST as of 2014. Please refer to the following - * document for more information: - * - * http://csrc.nist.gov/publications/nistpubs/800-131A/sp800-131A.pdf - * - * If you are uncertain about service providers supporting SHA-256 or other - * algorithms of the SHA-2 family, you can configure it individually in the - * SP-remote metadata set for those that support it. Once you are certain that - * all your configured SPs support SHA-2, you can safely remove the configuration - * options in the SP-remote metadata set and uncomment the following option. - * - * Please refer to the IdP hosted reference for more information. - */ - 'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', - - /* Uncomment the following to use the uri NameFormat on attributes. */ - /* - 'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', - 'authproc' => array( - // Convert LDAP names to oids. - 100 => array('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' => array( - 'authority' => 'urn:mace:example.org', - 'instant' => '2008-01-17T11:28:03Z', - 'policies' => array( - 'en' => 'http://example.org/policy', - 'es' => 'http://example.org/politica', - ), - ), - */ -); - -$metadata['__DYNAMIC:2__'] = array( - 'host' => 'nara.auth.dev.iww.org.uk', - - 'privatekey' => 'saml.pem', - 'certificate' => 'saml.crt', - - 'auth' => 'redcard', - - 'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', -); - -$metadata['__DYNAMIC:3__'] = array( - 'host' => 'auth.dev.iww.org.uk', - - 'privatekey' => 'saml.pem', - 'certificate' => 'saml.crt', - - 'auth' => 'default-sp', - - 'signature.algorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', -); diff --git a/metadata/saml20-idp-remote.php b/metadata/saml20-idp-remote.php deleted file mode 100644 index 9e65511..0000000 --- a/metadata/saml20-idp-remote.php +++ /dev/null @@ -1,60 +0,0 @@ - array( -// 'en' => 'WISE-RA (production)', -// ), -// 'description' => 'Log in using your WISE-RA members area user name.', -// 'SingleSignOnService' => 'https://service.iww.org.uk/simplesaml/saml2/idp/SSOService.php', -// 'SingleLogoutService' => 'https://service.iww.org.uk/simplesaml/saml2/idp/SingleLogoutService.php', -// 'privatekey' => 'saml.pem', -// 'certificate' => 'saml.crt', -// ); -$metadata['https://wisera.auth.dev.iww.org.uk/simplesaml/saml2/idp/metadata.php'] = array( - 'name' => array( - 'en' => 'WISE-RA (dev)', - ), - 'description' => 'Log in using your WISE-RA members area user name.', - 'SingleSignOnService' => 'https://wisera.auth.dev.iww.org.uk/simplesaml/saml2/idp/SSOService.php', - 'SingleLogoutService' => 'https://wisera.auth.dev.iww.org.uk/simplesaml/saml2/idp/SingleLogoutService.php', - 'privatekey' => 'saml.pem', - 'certificate' => 'saml.crt', -); -$metadata['https://nara.auth.dev.iww.org.uk/simplesaml/saml2/idp/metadata.php'] = array( - 'name' => array( - 'en' => 'NARA (dev)', - ), - 'description' => 'Log in using your NARA red card username.', - 'SingleSignOnService' => 'https://nara.auth.dev.iww.org.uk/simplesaml/saml2/idp/SSOService.php', - 'SingleLogoutService' => 'https://nara.auth.dev.iww.org.uk/simplesaml/saml2/idp/SingleLogoutService.php', - 'privatekey' => 'saml.pem', - 'certificate' => 'saml.crt', -); - -/* - * Guest IdP. allows users to sign up and register. Great for testing! - */ -/* -$metadata['https://openidp.feide.no'] = array( - 'name' => array( - 'en' => 'Feide OpenIdP - guest users', - 'no' => 'Feide Gjestebrukere', - ), - 'description' => 'Here you can login with your account on Feide RnD OpenID. If you do not already have an account on this identity provider, you can create a new one by following the create new account link and follow the instructions.', - - 'SingleSignOnService' => 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', - 'SingleLogoutService' => 'https://openidp.feide.no/simplesaml/saml2/idp/SingleLogoutService.php', - 'certFingerprint' => 'c9ed4dfb07caf13fc21e0fec1572047eb8a7a4cb' -); -*/ - diff --git a/metadata/saml20-sp-remote.php b/metadata/saml20-sp-remote.php deleted file mode 100644 index c0bc787..0000000 --- a/metadata/saml20-sp-remote.php +++ /dev/null @@ -1,96 +0,0 @@ - 'https://auth.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp', - 'SingleLogoutService' => 'https://auth.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp', -); - -/* - * MediaWiki - */ -$metadata['https://mediawiki.dev.iww.org.uk/simplesaml/module.php/saml/sp/metadata.php/default-sp'] = array( - 'AssertionConsumerService' => 'https://mediawiki.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp', - 'SingleLogoutService' => 'https://mediawiki.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp', -); - -/* - * Moodle - */ -$metadata['https://moodle.dev.iww.org.uk/simplesaml/module.php/saml/sp/metadata.php/default-sp'] = array( - 'AssertionConsumerService' => 'https://moodle.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp', - 'SingleLogoutService' => 'https://moodle.dev.iww.org.uk/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp', -); - -/* - * WordPress - */ - $metadata['urn:dev.iww.org.uk'] = array( - 'AssertionConsumerService' => 'https://dev.iww.org.uk/wp/wp-login.php', - 'SingleLogoutService' => 'https://dev.iww.org.uk/wp/wp-login.php', -); -$metadata['urn:shop.dev.iww.org.uk'] = array( - 'AssertionConsumerService' => 'https://shop.dev.iww.org.uk/wp/wp-login.php', - 'SingleLogoutService' => 'https://shop.dev.iww.org.uk/wp/wp-login.php', -); - -/* - * Nextcloud - */ -$metadata['https://cloud.dev.iww.org.uk/apps/user_saml/saml/metadata'] = array( - 'AssertionConsumerService' => 'https://cloud.dev.iww.org.uk/apps/user_saml/saml/acs', - 'SingleLogoutService' => 'https://cloud.dev.iww.org.uk/apps/user_saml/saml/sls', -); - -/* - * RocketChat - */ -$metadata['https://chat.dev.iww.org.uk/_saml/metadata/rc'] = array ( - 'entityid' => 'https://chat.dev.iww.org.uk/_saml/metadata/rc', - 'contacts' => array (), - 'metadata-set' => 'saml20-sp-remote', - 'AssertionConsumerService' => array ( - 0 => array ( - 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - 'Location' => 'https://chat.dev.iww.org.uk/_saml/validate/rc', - 'index' => 1, - 'isDefault' => true, - ), - ), - 'SingleLogoutService' => array ( - 0 => array ( - 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - 'Location' => 'https://chat.dev.iww.org.uk/_saml/logout/rc/', - 'ResponseLocation' => 'https://chat.dev.iww.org.uk/_saml/logout/rc/', - ), - ), - 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', -); - -/* - * Example SimpleSAMLphp SAML 2.0 SP - */ -/* $metadata['https://saml2sp.example.org'] = array( - 'AssertionConsumerService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp', - 'SingleLogoutService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp', -); */ - -/* - * This example shows an example config that works with Google Apps for education. - * What is important is that you have an attribute in your IdP that maps to the local part of the email address - * at Google Apps. In example, if your google account is foo.com, and you have a user that has an email john@foo.com, then you - * must set the simplesaml.nameidattribute to be the name of an attribute that for this user has the value of 'john'. - */ -/* $metadata['google.com'] = array( - 'AssertionConsumerService' => 'https://www.google.com/a/g.feide.no/acs', - 'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', - 'simplesaml.nameidattribute' => 'uid', - 'simplesaml.attributes' => FALSE, -); */