diff --git a/_example/etc/handler-nginx.conf b/_example/etc/handler-nginx.conf index ed77da070..5e8a4e985 100644 --- a/_example/etc/handler-nginx.conf +++ b/_example/etc/handler-nginx.conf @@ -32,7 +32,7 @@ server { #real_ip_header X-Forwarded-For; location = /reload { - allow 127.0.0.1/8; + allow 127.0.0.0/8; allow ::1/128; deny all; diff --git a/doc/sources/admin/adaptativeauthenticationlevel.rst b/doc/sources/admin/adaptativeauthenticationlevel.rst new file mode 100644 index 000000000..584d593fe --- /dev/null +++ b/doc/sources/admin/adaptativeauthenticationlevel.rst @@ -0,0 +1,46 @@ +Adaptative Authentication Level +=============================== + +Presentation +------------ + +A user obtain an authentication level depending on which authentication +module was used, and eventually which second factor module. + +This plugin allows to adapt this authentication level depending on +other conditions, like network, device, etc. + +Sample use case: a strategic application is configured to require an +authentication level of 5. Users obtain level 2 with their login/password +and level 5 using a TOTP second factor. You can trust users form internal +network by incrementing their authentication level based on their IP address, +they would then not be forced to use 2FA to access the strategic application. + +.. tip:: + + This use case works if you enable the *Use 2FA for session upgrade* option. + +Configuration +------------- + +This plugin is enabled when at least one rule is defind. + +To configure rules, go in ``General Parameters`` > ``Plugins`` > +``Adapative Authentication Level``. + +You can then create rules with these fields: + +- **Rule**: The condition that will be evaluated. If this condition + does not return true, then the level is not changed. +- **Value**: How change the authentication level. First character is + ``+``, ``-`` or ``=``, the second part is the number to add, remove + or set. + + +.. tip:: + + By example, to add 3 to authentication level for users from 192.168.0.0/24 network: + + - Rule: ``$env->{REMOTE_ADDR} =~ /^192\.168\./`` + - Value: ``+3`` + diff --git a/doc/sources/admin/applications/jitsimeet.rst b/doc/sources/admin/applications/jitsimeet.rst index 067299a7e..7b24221ca 100644 --- a/doc/sources/admin/applications/jitsimeet.rst +++ b/doc/sources/admin/applications/jitsimeet.rst @@ -95,6 +95,20 @@ configuration file: proxy_pass http://127.0.0.1:8888/login; } + +.. warning:: + + Thoses 2 blocks should be append BEFORE the following block:: + + #Anything that didn't match above, and isn't a real file, + #assume it's a room name and redirect to / + location ~ ^/([^/?&:'"]+)/(.*)$ { + set $subdomain "$1."; + set $subdir "$1/"; + rewrite ^/([^/?&:'"]+)/(.*)$ /$2; + } + + Jitsi Meet Virtual host in Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/sources/admin/authsaml.rst b/doc/sources/admin/authsaml.rst index 68374ffd2..3e780cbc1 100644 --- a/doc/sources/admin/authsaml.rst +++ b/doc/sources/admin/authsaml.rst @@ -175,6 +175,7 @@ Signature These options override service signature options (see :ref:`SAML service configuration`). +- **Signature method**: signature method for requests sent to this provider - **Sign SSO message**: sign SSO message - **Check SSO message signature**: check SSO message signature - **Sign SLO message**: sign SLO message diff --git a/doc/sources/admin/behindproxyminihowto.rst b/doc/sources/admin/behindproxyminihowto.rst index 45b12683d..42120237b 100644 --- a/doc/sources/admin/behindproxyminihowto.rst +++ b/doc/sources/admin/behindproxyminihowto.rst @@ -6,6 +6,9 @@ Your network infrastructure might require that LemonLDAP::NG components |image0| +Transmitting the correct IP address to the portal +------------------------------------------------- + In this case, LemonLDAP::NG components will store the ip address of the connection between the reverse proxy and the webserver in the session, and in logs. This prevents features such as session restrictions and @@ -19,7 +22,7 @@ to forward the original IP address all the way to LemonLDAP::NG. In order to do this you have several options. HTTP Header ------------ +~~~~~~~~~~~ This generic method is the most likely to work in your particular environment. @@ -68,7 +71,7 @@ uncomment the relevant parts of the configuration file. module documentation carefully. PROXY Protocol --------------- +~~~~~~~~~~~~~~ Alternatively, if your proxy supports the PROXY protocol (Nginx, HAProxy, Amazon ELB), you may use it to carry over the information @@ -86,6 +89,41 @@ Portal/Manager/Handler: # or # listen 443 ssl proxy_protocol; + set_real_ip_from 127.0.0.1; + real_ip_header proxy_protocol; + .. |image0| image:: /documentation/reverseproxy.png :class: align-center + +Fixing handler redirections +--------------------------- + +If your handler server runs behind a reverse proxy, it may have trouble figuring +out the right URL to redirect you to after logging in. + +In this case, you can force a particular port and scheme in the Virtual Host's +Options. + +But is instead you want this scheme to be auto-detected by LemonLDAP (in order to have a +same VHost domain available over multiple schemes), you can also use the following +declarations in the handler's virtual host to force LemonLDAP to use the correct port +and scheme + +Nginx +~~~~~ + +:: + + fastcgi_param SERVER_PORT 443 + fastcgi_param HTTPS On + +Apache +~~~~~~ + +.. versionadded: 2.0.10 + +:: + + PerlSetEnv SERVER_PORT 443 + PerlSetEnv HTTPS On diff --git a/doc/sources/admin/confignginx.rst b/doc/sources/admin/confignginx.rst index 066d3f5f7..a206b82e4 100644 --- a/doc/sources/admin/confignginx.rst +++ b/doc/sources/admin/confignginx.rst @@ -27,7 +27,7 @@ Red Hat/CentOS :: - yum install lemonldap-ng-fastcgi-server + yum install lemonldap-ng-nginx lemonldap-ng-fastcgi-server Enable and start the service : @@ -40,25 +40,25 @@ Files ----- With tarball installation, Nginx configuration files will be installed -in ``/usr/local/lemonldap-ng/etc/``, else they are in -``/etc/lemonldap-ng``. - -You have to include them in Nginx main configuration. +in ``/usr/local/lemonldap-ng/etc/``, else they are directly in web server +configuration. .. _debianubuntu-1: Debian/Ubuntu ~~~~~~~~~~~~~ -Link files into ``sites-available`` directory (should already have been -done if you used packages): +- Install log format *(automatically loaded when linked in this place)* :: - ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/ - ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/ - ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/ - ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/ + ln -s /etc/lemonldap-ng/nginx-lmlog.conf /etc/nginx/conf.d/llng-lmlog.conf + +- Install snippet for vhost configuration files: + +:: + + ln -s /etc/lemonldap-ng/nginx-lua-headers.conf /etc/nginx/snippets/llng-lua-headers.conf - Enable sites: @@ -68,18 +68,3 @@ done if you used packages): ln -s /etc/nginx/sites-available/manager-nginx.conf /etc/nginx/sites-enabled/ ln -s /etc/nginx/sites-available/portal-nginx.conf /etc/nginx/sites-enabled/ ln -s /etc/nginx/sites-available/test-nginx.conf /etc/nginx/sites-enabled/ - -.. _red-hatcentos-1: - -Red Hat/CentOS -~~~~~~~~~~~~~~ - -Link files directly in ``conf.d`` directory: - -:: - - ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/conf.d/ - ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/conf.d/ - ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/conf.d/ - ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/conf.d/ - diff --git a/doc/sources/admin/configvhost.rst b/doc/sources/admin/configvhost.rst index de8e0201a..8c03c87d3 100644 --- a/doc/sources/admin/configvhost.rst +++ b/doc/sources/admin/configvhost.rst @@ -261,7 +261,7 @@ Example of a protected virtual host for a local application: Reverse proxy ~~~~~~~~~~~~~ -\* Example of a protected reverse-proxy: +- Example of a protected reverse-proxy: .. code-block:: nginx @@ -300,7 +300,7 @@ Reverse proxy # PASSING HEADERS TO APPLICATION # ################################## # IF LUA IS SUPPORTED - #include /path/to/nginx-lua-headers.conf + #include /path/to/nginx-lua-headers.conf; # ELSE # Set manually your headers @@ -309,8 +309,16 @@ Reverse proxy } } -\* Example of a Nginx Virtual Host using uWSGI with many URIs protected -by different types of handler : +If /etc/nginx/proxy_params file does not exist, you can create it with this content: + +.. code-block:: nginx + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + +- Example of a Nginx Virtual Host using uWSGI with many URIs protected by different types of handler: .. code-block:: nginx diff --git a/doc/sources/admin/contextswitching.rst b/doc/sources/admin/contextswitching.rst index abeb819c3..3c92b39db 100644 --- a/doc/sources/admin/contextswitching.rst +++ b/doc/sources/admin/contextswitching.rst @@ -15,7 +15,8 @@ can be forbidden to assume. - **Parameters**: - - **Use rule**: Select which users may use this plugin + - **Use rule**: Rule to enable or define which users may use this plugin + (By example: $uid eq 'dwho' && $authenticationLevel > 2). - **Identities use rule**: Rule to define which identities can be assumed. Useful to prevent impersonation of certain sensitive identities like CEO, administrators or anonymous/protected users. @@ -30,7 +31,7 @@ can be forbidden to assume. During context switching authentication process, all plugins are disabled. In other words, all entry points like afterData, endAuth and so on are skipped. Therefore, second factors or - notifications by example will not be prompted! + notifications by example will not be prompted and login history is not updated! .. attention:: @@ -39,6 +40,11 @@ can be forbidden to assume. backend. You can not switch context with federated authentication. +.. attention:: + + Used identity, start and end of switching context process are logged! + + contextSwitchingPrefix is used to store real user's session Id. You can set this prefix ('switching' by default) by editing ``lemonldap-ng.ini`` in [portal] section: diff --git a/doc/sources/admin/idpsaml.rst b/doc/sources/admin/idpsaml.rst index d3db06b7d..dbcd88079 100644 --- a/doc/sources/admin/idpsaml.rst +++ b/doc/sources/admin/idpsaml.rst @@ -154,6 +154,7 @@ Signature These options override service signature options (see :ref:`SAML service configuration`). +- **Signature method**: signature method for messages sent to this service - **Sign SSO message**: sign SSO message - **Check SSO message signature**: check SSO message signature - **Sign SLO message**: sign SLO message diff --git a/doc/sources/admin/index_plugins.rst b/doc/sources/admin/index_plugins.rst index e649f1d6a..773db006d 100644 --- a/doc/sources/admin/index_plugins.rst +++ b/doc/sources/admin/index_plugins.rst @@ -4,6 +4,7 @@ Plugins .. toctree:: :maxdepth: 1 + adaptativeauthenticationlevel autosignin bruteforceprotection cda diff --git a/doc/sources/admin/logs.rst b/doc/sources/admin/logs.rst index fe79c8052..a0d504ca4 100644 --- a/doc/sources/admin/logs.rst +++ b/doc/sources/admin/logs.rst @@ -6,8 +6,8 @@ Presentation Main settings: -- **REMOTE_USER** : session attribute used for logging user access. -- **REMOTE_CUSTOM** : can be used for logging a second user attribute +- **REMOTE_USER** : session attribute used for logging user access +- **REMOTE_CUSTOM** : can be used for logging an another user attribute or a macro (optional) - **Hidden attributes** : session attributes never displayed or sent diff --git a/doc/sources/admin/radius2f-inwebo.rst b/doc/sources/admin/radius2f-inwebo.rst new file mode 100644 index 000000000..8abcb8629 --- /dev/null +++ b/doc/sources/admin/radius2f-inwebo.rst @@ -0,0 +1,27 @@ +InWebo Second Factor +==================== + +`InWebo `_ is a proprietary MFA solution. +You can use is as second factor trough :doc:`Radius 2FA module`. + +Configuration +~~~~~~~~~~~~~ + +On InWebo side : + +- Create a connector of type ``Radius Push``. +- Fill in the “IP Address” field with the IP of the public interface of your LL::NG server. +- Enter a secret, that you will also configure on LL::NG side. + +See `InWebo Radius documentation `_ for more details. + +On LL::NG side, go in "General Parameters » Second factors » +Radius second factor". + +- **Activation**: Set to ``On`` to activate this module, or use a + specific rule to select which users may use this type of second + factor +- **Server hostname**: The hostname of InWebo Radius server (for example `radius2.myinwebo.com`) +- **Shared secret**: The secret key declared on InWebo side + +See :doc:`Radius 2FA module` for more details. diff --git a/doc/sources/admin/radius2f.rst b/doc/sources/admin/radius2f.rst index 4d1ed0cfc..572139ab1 100644 --- a/doc/sources/admin/radius2f.rst +++ b/doc/sources/admin/radius2f.rst @@ -63,3 +63,13 @@ Mail second factor". - **Logo** (Optional): logo file *(in static/ directory)* - **Label** (Optional): label that should be displayed to the user on the choice screen + +Vendor specific +~~~~~~~~~~~~~~~ + +Some configuration examples for specific vendors: + +.. toctree:: + :maxdepth: 1 + + radius2f-inwebo diff --git a/doc/sources/admin/samlservice.rst b/doc/sources/admin/samlservice.rst index 29eee3d6c..9ca56ccf6 100644 --- a/doc/sources/admin/samlservice.rst +++ b/doc/sources/admin/samlservice.rst @@ -121,8 +121,12 @@ encryption. To define keys, you can: - import your own private and public keys (``Replace by file`` input) -- generate new public and private keys (``New keys`` button) +- generate new public and private keys (``New certificate`` button) +.. versionchanged:: 2.0.10 + + A X.509 certificate is now generated instead of a plain public key. It has + 20 years of validity, and is self signed with the 2048bit RSA key. .. tip:: @@ -132,34 +136,35 @@ To define keys, you can: |image1| -You can import a certificate containing the public key instead the raw -public key. However, certificate will not be really validated by other -SAML components (expiration date, common name, etc.), but will just be a -public key wrapper. -.. tip:: +- **Use certificate in response**: Certificate will be sent inside SAML + responses. +- **Signature method**: set the signature algorithm - You can easily generate a certificate to replace your public - key by saving the private key in a file, and use ``openssl`` commands to - issue a self-signed certificate: +.. versionchanged:: 2.0.10 - :: + The signature method can now be overriden for a SP or IDP. This will only work + if you are using a certificate for signature instead of a public key. - $ openssl req -new -key private.key -out cert.pem -x509 -days 3650 +.. attention:: + + If you are running a version under 2.0.10, the choice of a signature + algorithm will affect all SP and IDP. +Converting a RSA public key to a certificate +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- **Use certificate in response**: Certificate will be sent inside SAML - responses. -- **Signature method**: set the signature algorithm +If your application complains about the lack of certificate in SAML Metadatas, and you generated a public RSA key instead of a certificate in a previous version of LemonLDAP::NG, you can convert the public key into a certificate without changing the private key. +Save the private key in a file, and use the ``openssl`` commands to +issue a self-signed certificate: -.. attention:: + :: + + $ openssl req -new -key private.key -out cert.pem -x509 -days 3650 - Default value is RSA SHA1 for compatibility purpose but - we recommend to use RSA SHA256. This requires to test all partners to - check their compatibility. NameID formats ~~~~~~~~~~~~~~ diff --git a/fastcgi-server/psgi/llngapp.psgi b/fastcgi-server/psgi/llngapp.psgi index ba3421a12..8e7009c50 100644 --- a/fastcgi-server/psgi/llngapp.psgi +++ b/fastcgi-server/psgi/llngapp.psgi @@ -1,6 +1,6 @@ # Plack app to use with Nginx # -# This app can remplace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI, +# This app can replace FastCGI server using Starman, Twiggy, uWSGI-Plugin-PSGI, # Feersum,... my %builder = ( diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm index 51914e5f7..1ee91ae74 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Constants.pm @@ -29,8 +29,8 @@ use constant DEFAULTCONFBACKEND => "File"; use constant DEFAULTCONFBACKENDOPTIONS => ( dirName => '/usr/local/lemonldap-ng/data/conf', ); -our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/; -our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/; +our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|ombModule)s)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/; +our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:State|User|XSS)|da)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/; our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' ); diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm index 30f982433..28eb3387f 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm @@ -312,7 +312,7 @@ sub defaultValues { 'samlOrganizationURL' => 'http://www.example.com', 'samlOverrideIDPEntityID' => '', 'samlRelayStateTimeout' => 600, - 'samlServiceSignatureMethod' => 'RSA_SHA1', + 'samlServiceSignatureMethod' => 'RSA_SHA256', 'samlSPSSODescriptorArtifactResolutionServiceArtifact' => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact', 'samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact' => diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm index 526a36db2..af3d21e7b 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/ReConstants.pm @@ -22,14 +22,14 @@ our $specialNodeHash = { }; our $doubleHashKeys = 'issuerDBGetParameters'; -our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|ombModule)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|S(?:MTPTLSOpts|SLVarIf))'; +our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|c(?:as(?:StorageOption|Attribute)|ustom(?:Plugins|Add)Param|ombModule)|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|macro)s|o(?:idcS(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|penIdExportedVars)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember|fExtra)|S(?:MTPTLSOpts|SLVarIf))'; our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaDataNode|virtualHost)s'; our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:(?:UserAttribut|Servic|Rul)e|AuthnLevel)|(?:ExportedVar|Macro)s)'; our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)'; our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))'; our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:uth(?:orizationCodeExpiration|nLevel)|llow(?:PasswordGrant|Offline)|ccessTokenExpiration|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)'; -our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ignS[LS]OMessage|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)'; -our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)'; +our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ign(?:S[LS]OMessage|atureMethod)|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)'; +our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:S(?:ign(?:S[LS]OMessage|atureMethod)|essionNotOnOrAfterTimeout)|N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|AuthnLevel|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)'; our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)'; our $authParameters = { diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/ApacheMP2/Request.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/ApacheMP2/Request.pm index 8d7e3cba3..496d4dba1 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/ApacheMP2/Request.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/ApacheMP2/Request.pm @@ -25,7 +25,7 @@ sub new { QUERY_STRING => $args, REQUEST_URI => $uri_full, PATH_INFO => '', - SERVER_PORT => $r->get_server_port, + SERVER_PORT => $ENV{SERVER_PORT} || $r->get_server_port, REQUEST_METHOD => $r->method, 'psgi.version' => [ 1, 1 ], 'psgi.url_scheme' => ( $ENV{HTTPS} || 'off' ) =~ /^(?:on|1)$/i diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm index 3d253c248..60979357d 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Run.pm @@ -614,7 +614,7 @@ sub _getPort { return $class->tsv->{port}->{_}; } else { - return $req->{env}->{SERVER_PORT}; + return $req->port; } } } @@ -638,7 +638,7 @@ sub _isHttps { return $class->tsv->{https}->{_}; } else { - return ( uc( $req->{env}->{HTTPS} || "OFF" ) eq "ON" ); + return $req->secure; } } } diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index 47b32dce7..d1f2ab31c 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -139,7 +139,7 @@ site/htdocs/static/forms/post.html site/htdocs/static/forms/postContainer.html site/htdocs/static/forms/README.md site/htdocs/static/forms/restore.html -site/htdocs/static/forms/RSAKey.html +site/htdocs/static/forms/RSACertKey.html site/htdocs/static/forms/RSAKeyNoPassword.html site/htdocs/static/forms/rule.html site/htdocs/static/forms/ruleContainer.html diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index c5e6707aa..054f4d93c 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -171,7 +171,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a- 'RSAPrivateKey' => { 'test' => sub { return $_[0] =~ -m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s +m[^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s ? 1 : ( 1, '__badPemEncoding__' ); } @@ -263,6 +263,18 @@ sub attributes { 'default' => 1, 'type' => 'bool' }, + 'adaptativeAuthenticationLevelRules' => { + 'keyMsgFail' => '__badRegexp__', + 'keyTest' => sub { + eval { + do { + qr/$_[0]/; + } + }; + return $@ ? 0 : 1; + }, + 'type' => 'keyTextContainer' + }, 'ADPwdExpireWarning' => { 'default' => 0, 'type' => 'int' @@ -1026,6 +1038,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] ], 'type' => 'select' }, + 'contextSwitchingAllowed2fModifications' => { + 'default' => 0, + 'type' => 'bool' + }, 'contextSwitchingIdRule' => { 'default' => 1, 'test' => sub { @@ -3203,6 +3219,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'default' => '', 'type' => 'longtext' }, + 'samlIDPMetaDataOptionsSignatureMethod' => { + 'default' => '', + 'select' => [ { + 'k' => '', + 'v' => 'default' + }, + { + 'k' => 'RSA_SHA1', + 'v' => 'RSA SHA1' + }, + { + 'k' => 'RSA_SHA256', + 'v' => 'RSA SHA256' + }, + { + 'k' => 'RSA_SHA384', + 'v' => 'RSA SHA384' + }, + { + 'k' => 'RSA_SHA512', + 'v' => 'RSA SHA512' + } + ], + 'type' => 'select' + }, 'samlIDPMetaDataOptionsSignSLOMessage' => { 'default' => -1, 'type' => 'trool' @@ -3394,7 +3435,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'type' => 'RSAPublicKeyOrCertificate' }, 'samlServiceSignatureMethod' => { - 'default' => 'RSA_SHA1', + 'default' => 'RSA_SHA256', 'select' => [ { 'k' => 'RSA_SHA1', 'v' => 'RSA SHA1' @@ -3402,6 +3443,14 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] { 'k' => 'RSA_SHA256', 'v' => 'RSA SHA256' + }, + { + 'k' => 'RSA_SHA384', + 'v' => 'RSA SHA384' + }, + { + 'k' => 'RSA_SHA512', + 'v' => 'RSA SHA512' } ], 'type' => 'select' @@ -3539,6 +3588,31 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.] 'default' => 72000, 'type' => 'int' }, + 'samlSPMetaDataOptionsSignatureMethod' => { + 'default' => '', + 'select' => [ { + 'k' => '', + 'v' => 'default' + }, + { + 'k' => 'RSA_SHA1', + 'v' => 'RSA SHA1' + }, + { + 'k' => 'RSA_SHA256', + 'v' => 'RSA SHA256' + }, + { + 'k' => 'RSA_SHA384', + 'v' => 'RSA SHA384' + }, + { + 'k' => 'RSA_SHA512', + 'v' => 'RSA SHA512' + } + ], + 'type' => 'select' + }, 'samlSPMetaDataOptionsSignSLOMessage' => { 'default' => -1, 'type' => 'trool' diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm index 5b849aa77..39147bf25 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -154,7 +154,7 @@ sub types { test => sub { return ( $_[0] =~ -/^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s +/^(?:(?:\-+\s*BEGIN\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?(?:Proc-Type:.*\r?\nDEK-Info:.*\r?\n[\r\n]*)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:(?:RSA|ENCRYPTED)\s+)?PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); @@ -594,6 +594,12 @@ sub attributes { documentation => 'Stop context switching by logout', flags => 'p', }, + contextSwitchingAllowed2fModifications => { + type => 'bool', + default => 0, + documentation => 'Allowed SFA modifications', + flags => 'p', + }, contextSwitchingPrefix => { type => 'text', default => 'switching', @@ -2119,6 +2125,18 @@ sub attributes { documentation => 'List of auto signin rules', }, + # Adaptative Authentication Level plugin + adaptativeAuthenticationLevelRules => { + type => 'keyTextContainer', + keyTest => sub { + eval { qr/$_[0]/ }; + return $@ ? 0 : 1; + }, + keyMsgFail => '__badRegexp__', + documentation => 'Adaptative authentication level rules', + flags => 'p', + }, + ## Virtualhosts # Fake attribute: used by manager REST API to agglomerate all other @@ -2490,8 +2508,10 @@ sub attributes { select => [ { k => 'RSA_SHA1', v => 'RSA SHA1' }, { k => 'RSA_SHA256', v => 'RSA SHA256' }, + { k => 'RSA_SHA384', v => 'RSA SHA384' }, + { k => 'RSA_SHA512', v => 'RSA SHA512' }, ], - default => 'RSA_SHA1', + default => 'RSA_SHA256', }, samlServiceUseCertificateInResponse => { type => 'bool', @@ -2764,6 +2784,17 @@ sub attributes { type => 'trool', default => -1, }, + samlIDPMetaDataOptionsSignatureMethod => { + type => 'select', + select => [ + { k => '', v => 'default' }, + { k => 'RSA_SHA1', v => 'RSA SHA1' }, + { k => 'RSA_SHA256', v => 'RSA SHA256' }, + { k => 'RSA_SHA384', v => 'RSA SHA384' }, + { k => 'RSA_SHA512', v => 'RSA SHA512' }, + ], + default => '', + }, samlIDPMetaDataOptionsCheckSLOMessageSignature => { type => 'bool', default => 1, @@ -2920,6 +2951,17 @@ sub attributes { type => 'trool', default => -1, }, + samlSPMetaDataOptionsSignatureMethod => { + type => 'select', + select => [ + { k => '', v => 'default' }, + { k => 'RSA_SHA1', v => 'RSA SHA1' }, + { k => 'RSA_SHA256', v => 'RSA SHA256' }, + { k => 'RSA_SHA384', v => 'RSA SHA384' }, + { k => 'RSA_SHA512', v => 'RSA SHA512' }, + ], + default => '', + }, samlSPMetaDataOptionsCheckSSOMessageSignature => { type => 'bool', default => 1, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm index 9a101d5a5..0023f9a6b 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/CTrees.pm @@ -52,6 +52,7 @@ sub cTrees { title => "samlIDPMetaDataOptionsSignature", form => 'simpleInputContainer', nodes => [ + "samlIDPMetaDataOptionsSignatureMethod", "samlIDPMetaDataOptionsSignSSOMessage", "samlIDPMetaDataOptionsCheckSSOMessageSignature", "samlIDPMetaDataOptionsSignSLOMessage", @@ -122,10 +123,11 @@ sub cTrees { title => "samlSPMetaDataOptionsSignature", form => 'simpleInputContainer', nodes => [ + "samlSPMetaDataOptionsSignatureMethod", "samlSPMetaDataOptionsSignSSOMessage", "samlSPMetaDataOptionsCheckSSOMessageSignature", "samlSPMetaDataOptionsSignSLOMessage", - "samlSPMetaDataOptionsCheckSLOMessageSignature" + "samlSPMetaDataOptionsCheckSLOMessageSignature", ] }, { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm index 9ea5b3e5e..d62ef1b2b 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Tree.pm @@ -615,6 +615,7 @@ sub tree { nodes => [ 'stayConnected', 'portalStatus', + 'adaptativeAuthenticationLevelRules', 'upgradeSession', 'refreshSessions', { @@ -792,6 +793,7 @@ sub tree { 'contextSwitchingRule', 'contextSwitchingIdRule', 'contextSwitchingUnrestrictedUsersRule', + 'contextSwitchingAllowed2fModifications', 'contextSwitchingStopWithLogout', ] }, @@ -812,7 +814,11 @@ sub tree { { title => 'secondFactors', help => 'secondfactor.html', - nodes => [ { + nodes => [ + 'sfManagerRule', + 'sfRequired', + 'sfOnlyUpgrade', + { title => 'utotp2f', help => 'utotp2f.html', form => 'simpleInputContainer', @@ -930,9 +936,6 @@ sub tree { 'sfRemovedNotifMsg', ], }, - 'sfOnlyUpgrade', - 'sfManagerRule', - 'sfRequired', ] }, { @@ -1074,7 +1077,7 @@ sub tree { help => 'samlservice.html#security-parameters', nodes => [ { title => 'samlServiceSecuritySig', - form => 'RSAKey', + form => 'RSACertKey', group => [ 'samlServicePrivateKeySig', 'samlServicePrivateKeySigPwd', @@ -1083,7 +1086,7 @@ sub tree { }, { title => 'samlServiceSecurityEnc', - form => 'RSAKey', + form => 'RSACertKey', group => [ 'samlServicePrivateKeyEnc', 'samlServicePrivateKeyEncPwd', diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm index 8ad2e3466..03263f5fc 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Cli.pm @@ -431,7 +431,6 @@ sub run { unless (@_) { die 'nothing to do, aborting'; } - $self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum ); my $action = shift; unless ( $action =~ /^(?:get|set|del|addKey|delKey|save|restore|rollback)$/ ) @@ -440,6 +439,12 @@ sub run { "Unknown action $action. Only get, set, del, addKey, delKey, save, restore, rollback allowed"; } + unless ( $action eq "restore" ) { + + # This step prevents restoring when config DB is empty (#2340) + $self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum ); + } + $self->$action(@_); } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm index 762bb4bd0..1702fc6a8 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm @@ -15,6 +15,7 @@ use Lemonldap::NG::Common::EmailTransport; use Crypt::OpenSSL::RSA; use Convert::PEM; use URI::URL; +use Net::SSLeay; use feature 'state'; @@ -59,10 +60,11 @@ sub init { # New key and conf save ->addRoute( confs => { - newRSAKey => 'newRSAKey', - sendTestMail => 'sendTestMail', - raw => 'newRawConf', - '*' => 'newConf' + newRSAKey => 'newRSAKey', + newCertificate => 'newCertificate', + sendTestMail => 'sendTestMail', + raw => 'newRawConf', + '*' => 'newConf' }, ['POST'] ) @@ -76,6 +78,31 @@ sub init { return 1; } +# 35 - New Certificate on demand +# -------------------------- + +##@method public PSGI-JSON-response newRSAKey($req) +# Return a hashref containing private and public keys +# The posted data must contain a JSON object containing +# {"password":"newpassword"} +# +#@param $req Lemonldap::NG::Common::PSGI::Request object +#@return PSGI JSON response +sub newCertificate { + my ( $self, $req, @others ) = @_; + return $self->sendError( $req, 'There is no subkey for "newCertificate"', + 400 ) + if (@others); + my $query = $req->jsonBodyToObj; + + my ( $private, $cert ) = $self->_generateX509( $query->{password} ); + my $keys = { + 'private' => $private, + 'public' => $cert, + }; + return $self->sendJSONresponse( $req, $keys ); +} + # 35 - New RSA key pair on demand # -------------------------- @@ -90,8 +117,9 @@ sub newRSAKey { my ( $self, $req, @others ) = @_; return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 ) if (@others); + my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); + my $query = $req->jsonBodyToObj; - my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); my $keys = { 'private' => $rsa->get_private_key_string(), 'public' => $rsa->get_public_key_x509_string(), @@ -121,6 +149,81 @@ sub newRSAKey { return $self->sendJSONresponse( $req, $keys ); } +# This function does the dirty X509 work, +# mostly copied from IO::Socket::SSL::Utils +# and adapter to work on old platforms (CentOS7) + +sub _generateX509 { + my ( $self, $password ) = @_; + Net::SSLeay::SSLeay_add_ssl_algorithms(); + my $conf = $self->confAcc->getConf(); + + # Generate 2048 bits RSA key + my $key = Net::SSLeay::EVP_PKEY_new(); + Net::SSLeay::EVP_PKEY_assign_RSA( $key, + Net::SSLeay::RSA_generate_key( 2048, 0x10001 ) ); + + my $cert = Net::SSLeay::X509_new(); + + # Serial + Net::SSLeay::ASN1_INTEGER_set( + Net::SSLeay::X509_get_serialNumber($cert), + rand( 2**32 ), + ); + + # Version + Net::SSLeay::X509_set_version( $cert, 2 ); + + # Make it last 20 years + Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notBefore($cert), + time() ); + Net::SSLeay::ASN1_TIME_set( Net::SSLeay::X509_get_notAfter($cert), + time() + 20 * 365 * 86400 ); + + # set subject + my $portal_uri = new URI::URL( $conf->{portal} || "http://localhost" ); + my $portal_host = $portal_uri->host; + my $subj_e = Net::SSLeay::X509_get_subject_name($cert); + my $subj = { commonName => $portal_host, }; + + while ( my ( $k, $v ) = each %$subj ) { + + # Not everything we get is nice - try with MBSTRING_UTF8 first and if it + # fails try V_ASN1_T61STRING and finally V_ASN1_OCTET_STRING + Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 0x1000, $v, -1, + 0 ) + or + Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 20, $v, -1, 0 ) + or + Net::SSLeay::X509_NAME_add_entry_by_txt( $subj_e, $k, 4, $v, -1, 0 ) + or croak( "failed to add entry for $k - " + . Net::SSLeay::ERR_error_string( Net::SSLeay::ERR_get_error() ) ); + } + + # Set to self-sign + Net::SSLeay::X509_set_pubkey( $cert, $key ); + Net::SSLeay::X509_set_issuer_name( $cert, + Net::SSLeay::X509_get_subject_name($cert) ); + + # Sign with default alg + Net::SSLeay::X509_sign( $cert, $key, 0 ); + + my $strCert = Net::SSLeay::PEM_get_string_X509($cert); + my $strPrivate; + if ($password) { + $strPrivate = Net::SSLeay::PEM_get_string_PrivateKey( $key, $password ); + } + else { + $strPrivate = Net::SSLeay::PEM_get_string_PrivateKey($key); + } + + # Free OpenSSL objects + Net::SSLeay::X509_free($cert); + Net::SSLeay::EVP_PKEY_free($key); + + return ( $strPrivate, $strCert ); +} + # Sending a test Email # -------------------- diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm index b2685fdc9..17207cab6 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Tests.pm @@ -380,6 +380,31 @@ sub tests { && $conf->{samlServicePublicKeySig} ); return 1; }, + samlSignatureOverrideNeedsCertificate => sub { + + return 1 if $conf->{samlServicePublicKeySig} =~ /CERTIFICATE/; + + my @offenders; + for my $idp ( keys %{ $conf->{samlIDPMetaDataOptions} } ) { + if ( $conf->{samlIDPMetaDataOptions}->{$idp} + ->{samlIDPMetaDataOptionsSignatureMethod} ) + { + push @offenders, $idp; + } + } + for my $sp ( keys %{ $conf->{samlSPMetaDataOptions} } ) { + if ( $conf->{samlSPMetaDataOptions}->{$sp} + ->{samlSPMetaDataOptionsSignatureMethod} ) + { + push @offenders, $sp; + } + } + return 1 unless @offenders; + return ( 0, + "Cannot set non-default signature method on " + . join( ", ", @offenders ) + . " unless SAML signature key is in certificate form" ); + }, # Try to parse combination with declared modules checkCombinations => sub { @@ -811,19 +836,27 @@ sub tests { # Cookie SameSite=None requires Secure flag # Same with SameSite=(auto) and SAML issuer in use SameSiteNoneWithSecure => sub { - return ( 1, 'SameSite value = None requires the secured flag' ) + return ( -1, 'SameSite value = None requires the secured flag' ) if ( getSameSite($conf) eq 'None' and !$conf->{securedCookie} ); return 1; }, # Secure cookies require HTTPS SecureCookiesRequireHttps => sub { - return ( 1, 'Secure cookies require a HTTPS portal URL' ) + return ( -1, 'Secure cookies require a HTTPS portal URL' ) if ( $conf->{securedCookie} == 1 and $conf->{portal} and $conf->{portal} !~ /^https:/ ); return 1; }, + + # Password module requires a password backend + passwordModuleNeedsBackend => sub { + return ( -1, 'Password module is enabled without password backend' ) + if ( $conf->{portalDisplayChangePassword} + and $conf->{passwordDB} eq 'Null' ); + return 1; + }, }; } diff --git a/lemonldap-ng-manager/site/coffee/manager.coffee b/lemonldap-ng-manager/site/coffee/manager.coffee index bba62e3ea..fe9f7c79d 100644 --- a/lemonldap-ng-manager/site/coffee/manager.coffee +++ b/lemonldap-ng-manager/site/coffee/manager.coffee @@ -691,6 +691,19 @@ llapp.controller 'TreeCtrl', [ console.log('Error sending test email') # RSA keys generation + $scope.newCertificate = -> + $scope.showModal('password.html').then -> + $scope.waiting = true + currentNode = $scope.currentNode + password = $scope.result + $http.post("#{window.confPrefix}/newCertificate", {"password": password}).then (response) -> + currentNode.data[0].data = response.data.private + currentNode.data[1].data = password + currentNode.data[2].data = response.data.public + $scope.waiting = false + , readError + , -> + console.log('New key cancelled') $scope.newRSAKey = -> $scope.showModal('password.html').then -> $scope.waiting = true diff --git a/lemonldap-ng-manager/site/htdocs/static/forms/RSAKey.html b/lemonldap-ng-manager/site/htdocs/static/forms/RSACertKey.html similarity index 98% rename from lemonldap-ng-manager/site/htdocs/static/forms/RSAKey.html rename to lemonldap-ng-manager/site/htdocs/static/forms/RSACertKey.html index 19062e62f..ba6851380 100644 --- a/lemonldap-ng-manager/site/htdocs/static/forms/RSAKey.html +++ b/lemonldap-ng-manager/site/htdocs/static/forms/RSACertKey.html @@ -33,7 +33,7 @@