From 89b25556dc6195022addeb3ec3dcdcca331ff2e6 Mon Sep 17 00:00:00 2001 From: Nikhil Maske Date: Fri, 5 Feb 2021 12:45:56 +0530 Subject: [PATCH] prod_settings_template: Move LDAP up into authentication, and merge part 1/2. --- docs/production/authentication-methods.md | 311 +++++++++++----------- zproject/prod_settings_template.py | 196 +++++++------- 2 files changed, 252 insertions(+), 255 deletions(-) diff --git a/docs/production/authentication-methods.md b/docs/production/authentication-methods.md index a80cdc404d..91a9b412b1 100644 --- a/docs/production/authentication-methods.md +++ b/docs/production/authentication-methods.md @@ -35,161 +35,6 @@ Each of these requires one to a handful of lines of configuration in `settings.py`, as well as a secret in `zulip-secrets.conf`. Details are documented in your `settings.py`. -## SAML - -Zulip 2.1 and later supports SAML authentication, used by Okta, -OneLogin, and many other IdPs (identity providers). You can configure -it as follows: - -1. These instructions assume you have an installed Zulip server; if - you're using Zulip Cloud, see [this article][saml-help-center], - which also has IdP-side configuration advice for common IdPs. - - You can have created a Zulip organization already using the default - EmailAuthBackend, or plan to create the organization using SAML - authentication. - -1. Tell your IdP how to find your Zulip server: - - * **SP Entity ID**: `https://yourzulipdomain.example.com`. - - The `Entity ID` should match the value of - `SOCIAL_AUTH_SAML_SP_ENTITY_ID` computed in the Zulip settings. - You can get the correct value by running the following: - `/home/zulip/deployments/current/scripts/get-django-setting - SOCIAL_AUTH_SAML_SP_ENTITY_ID`. - - * **SSO URL**: - `https://yourzulipdomain.example.com/complete/saml/`. This is - the "SAML ACS url" in SAML terminology. - - If you're - [hosting multiple organizations](../production/multiple-organizations.html#authentication), - you need to use `SOCIAL_AUTH_SUBDOMAIN`. For example, - if `SOCIAL_AUTH_SUBDOMAIN="auth"` and `EXTERNAL_HOST=zulip.example.com`, - this should be `https://auth.zulip.example.com/complete/saml/`. - -2. Tell Zulip how to connect to your SAML provider(s) by filling - out the section of `/etc/zulip/settings.py` on your Zulip server - with the heading "SAML Authentication". - * You will need to update `SOCIAL_AUTH_SAML_ORG_INFO` with your - organization name (`displayname` may appear in the IdP's - authentication flow; `name` won't be displayed to humans). - * Fill out `SOCIAL_AUTH_SAML_ENABLED_IDPS` with data provided by - your identity provider. You may find [the python-social-auth - SAML - docs](https://python-social-auth.readthedocs.io/en/latest/backends/saml.html) - helpful. You'll need to obtain several values from your IdP's - metadata and enter them on the right-hand side of this - Python dictionary: - 1. Set the outer `idp_name` key to be an identifier for your IdP, - e.g. `testshib` or `okta`. This field appears in URLs for - parts of your Zulip server's SAML authentication flow. - 2. The IdP should provide the `url` and `entity_id` values. - 3. Save the `x509cert` value to a file; you'll use it in the - instructions below. - 4. The values needed in the `attr_` fields are often configurable - in your IdP's interface when setting up SAML authentication - (referred to as "Attribute Statements" with Okta, or - "Attribute Mapping" with GSuite). You'll want to connect - these so that Zulip gets the email address (used as a unique - user ID) and name for the user. - 5. The `display_name` and `display_icon` fields are used to - display the login/registration buttons for the IdP. - -3. Install the certificate(s) required for SAML authentication. You - will definitely need the public certificate of your IdP. Some IdP - providers also support the Zulip server (Service Provider) having - a certificate used for encryption and signing. We detail these - steps as optional below, because they aren't required for basic - setup, and some IdPs like Okta don't fully support Service - Provider certificates. You should install them as follows: - - 1. On your Zulip server, `mkdir -p /etc/zulip/saml/idps/` - 2. Put the IDP public certificate in `/etc/zulip/saml/idps/{idp_name}.crt` - 3. (Optional) Put the Zulip server public certificate in `/etc/zulip/saml/zulip-cert.crt` - 4. (Optional) Put the Zulip server private key in `/etc/zulip/saml/zulip-private-key.key` - 5. Set the proper permissions on these files and directories: - - ``` - chown -R zulip.zulip /etc/zulip/saml/ - find /etc/zulip/saml/ -type f -exec chmod 644 -- {} + - chmod 640 /etc/zulip/saml/zulip-private-key.key - ``` - -4. (Optional) If you configured the optional public and private server - certificates above, you can enable the additional setting - `"authnRequestsSigned": True` in `SOCIAL_AUTH_SAML_SECURITY_CONFIG` - to have the SAMLRequests the server will be issuing to the IdP - signed using those certificates. Additionally, if the IdP supports - it, you can upload the public certificate to enable encryption of - assertions in the SAMLResponses the IdP will send about - authenticated users. - -5. Enable the `zproject.backends.SAMLAuthBackend` auth backend, in -`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`. - -6. [Restart the Zulip server](../production/settings.md) to ensure -your settings changes take effect. The Zulip login page should now -have a button for SAML authentication that you can use to log in or -create an account (including when creating a new organization). - -7. If the configuration was successful, the server's metadata can be -found at `https://yourzulipdomain.example.com/saml/metadata.xml`. You -can use this for verifying your configuration or provide it to your -IdP. - -[saml-help-center]: https://zulip.com/help/saml-authentication - -### IdP-initiated SSO - -The above configuration is sufficient for Service Provider initialized -SSO, i.e. you can visit the Zulip webapp and click "Sign in with -{IdP}" and it'll correctly start the authentication flow. If you are -not hosting multiple organizations, with Zulip 3.0+, the above -configuration is also sufficient for Identity Provider initiated SSO, -i.e. clicking a "Sign in to Zulip" button on the IdP's website can -correctly authenticate the user to Zulip. - -If you're hosting multiple organizations and thus using the -`SOCIAL_AUTH_SUBDOMAIN` setting, you'll need to configure a custom -`RelayState` in your IdP of the form `{"subdomain": -"yourzuliporganization"}` to let Zulip know which organization to -authenticate the user to when they visit your SSO URL from the IdP. -(If the organization is on the root domain, use the empty string: -`{"subdomain": ""}`.). - -```eval_rst -.. _ldap: -``` - -### Restricting access to specific organizations - -If you're hosting multiple Zulip organizations, you can restrict which -organizations can use a given IdP by setting `limit_to_subdomains`. -For example, `limit_to_subdomains = ["", "engineering"]` would -restrict an IdP the root domain and the `engineering` subdomain. - -You can achieve the same goal with a SAML attribute; just declare -which attribute using `attr_org_membership` in the IdP configuration. -For the root subdomain, `www` in the list will work, or any other of -`settings.ROOT_SUBDOMAIN_ALIASES`. - -For example, with `attr_org_membership` set to `member`, a user with -the following attribute in their `AttributeStatement` will have access -to the root and `engineering` subdomains: - -``` - - - www - - - engineering - - -``` - ## LDAP (including Active Directory) Zulip supports retrieving information about users via LDAP, and @@ -459,6 +304,162 @@ the bottom of the problem: this file (feel free to anonymize any email addresses to `username@example.com`) in your report. +## SAML + +Zulip 2.1 and later supports SAML authentication, used by Okta, +OneLogin, and many other IdPs (identity providers). You can configure +it as follows: + +1. These instructions assume you have an installed Zulip server; if + you're using Zulip Cloud, see [this article][saml-help-center], + which also has IdP-side configuration advice for common IdPs. + + You can have created a Zulip organization already using the default + EmailAuthBackend, or plan to create the organization using SAML + authentication. + +1. Tell your IdP how to find your Zulip server: + + * **SP Entity ID**: `https://yourzulipdomain.example.com`. + + The `Entity ID` should match the value of + `SOCIAL_AUTH_SAML_SP_ENTITY_ID` computed in the Zulip settings. + You can get the correct value by running the following: + `/home/zulip/deployments/current/scripts/get-django-setting + SOCIAL_AUTH_SAML_SP_ENTITY_ID`. + + * **SSO URL**: + `https://yourzulipdomain.example.com/complete/saml/`. This is + the "SAML ACS url" in SAML terminology. + + If you're + [hosting multiple organizations](../production/multiple-organizations.html#authentication), + you need to use `SOCIAL_AUTH_SUBDOMAIN`. For example, + if `SOCIAL_AUTH_SUBDOMAIN="auth"` and `EXTERNAL_HOST=zulip.example.com`, + this should be `https://auth.zulip.example.com/complete/saml/`. + +2. Tell Zulip how to connect to your SAML provider(s) by filling + out the section of `/etc/zulip/settings.py` on your Zulip server + with the heading "SAML Authentication". + * You will need to update `SOCIAL_AUTH_SAML_ORG_INFO` with your + organization name (`displayname` may appear in the IdP's + authentication flow; `name` won't be displayed to humans). + * Fill out `SOCIAL_AUTH_SAML_ENABLED_IDPS` with data provided by + your identity provider. You may find [the python-social-auth + SAML + docs](https://python-social-auth.readthedocs.io/en/latest/backends/saml.html) + helpful. You'll need to obtain several values from your IdP's + metadata and enter them on the right-hand side of this + Python dictionary: + 1. Set the outer `idp_name` key to be an identifier for your IdP, + e.g. `testshib` or `okta`. This field appears in URLs for + parts of your Zulip server's SAML authentication flow. + 2. The IdP should provide the `url` and `entity_id` values. + 3. Save the `x509cert` value to a file; you'll use it in the + instructions below. + 4. The values needed in the `attr_` fields are often configurable + in your IdP's interface when setting up SAML authentication + (referred to as "Attribute Statements" with Okta, or + "Attribute Mapping" with GSuite). You'll want to connect + these so that Zulip gets the email address (used as a unique + user ID) and name for the user. + 5. The `display_name` and `display_icon` fields are used to + display the login/registration buttons for the IdP. + +3. Install the certificate(s) required for SAML authentication. You + will definitely need the public certificate of your IdP. Some IdP + providers also support the Zulip server (Service Provider) having + a certificate used for encryption and signing. We detail these + steps as optional below, because they aren't required for basic + setup, and some IdPs like Okta don't fully support Service + Provider certificates. You should install them as follows: + + 1. On your Zulip server, `mkdir -p /etc/zulip/saml/idps/` + 2. Put the IDP public certificate in `/etc/zulip/saml/idps/{idp_name}.crt` + 3. (Optional) Put the Zulip server public certificate in `/etc/zulip/saml/zulip-cert.crt` + 4. (Optional) Put the Zulip server private key in `/etc/zulip/saml/zulip-private-key.key` + 5. Set the proper permissions on these files and directories: + + ``` + chown -R zulip.zulip /etc/zulip/saml/ + find /etc/zulip/saml/ -type f -exec chmod 644 -- {} + + chmod 640 /etc/zulip/saml/zulip-private-key.key + ``` + +4. (Optional) If you configured the optional public and private server + certificates above, you can enable the additional setting + `"authnRequestsSigned": True` in `SOCIAL_AUTH_SAML_SECURITY_CONFIG` + to have the SAMLRequests the server will be issuing to the IdP + signed using those certificates. Additionally, if the IdP supports + it, you can upload the public certificate to enable encryption of + assertions in the SAMLResponses the IdP will send about + authenticated users. + +5. Enable the `zproject.backends.SAMLAuthBackend` auth backend, in +`AUTHENTICATION_BACKENDS` in `/etc/zulip/settings.py`. + +6. [Restart the Zulip server](../production/settings.md) to ensure +your settings changes take effect. The Zulip login page should now +have a button for SAML authentication that you can use to log in or +create an account (including when creating a new organization). + +7. If the configuration was successful, the server's metadata can be +found at `https://yourzulipdomain.example.com/saml/metadata.xml`. You +can use this for verifying your configuration or provide it to your +IdP. + +[saml-help-center]: https://zulip.com/help/saml-authentication + +### IdP-initiated SSO + +The above configuration is sufficient for Service Provider initialized +SSO, i.e. you can visit the Zulip webapp and click "Sign in with +{IdP}" and it'll correctly start the authentication flow. If you are +not hosting multiple organizations, with Zulip 3.0+, the above +configuration is also sufficient for Identity Provider initiated SSO, +i.e. clicking a "Sign in to Zulip" button on the IdP's website can +correctly authenticate the user to Zulip. + +If you're hosting multiple organizations and thus using the +`SOCIAL_AUTH_SUBDOMAIN` setting, you'll need to configure a custom +`RelayState` in your IdP of the form `{"subdomain": +"yourzuliporganization"}` to let Zulip know which organization to +authenticate the user to when they visit your SSO URL from the IdP. +(If the organization is on the root domain, use the empty string: +`{"subdomain": ""}`.). + +```eval_rst +.. _ldap: +``` + +### Restricting access to specific organizations + +If you're hosting multiple Zulip organizations, you can restrict which +organizations can use a given IdP by setting `limit_to_subdomains`. +For example, `limit_to_subdomains = ["", "engineering"]` would +restrict an IdP the root domain and the `engineering` subdomain. + +You can achieve the same goal with a SAML attribute; just declare +which attribute using `attr_org_membership` in the IdP configuration. +For the root subdomain, `www` in the list will work, or any other of +`settings.ROOT_SUBDOMAIN_ALIASES`. + +For example, with `attr_org_membership` set to `member`, a user with +the following attribute in their `AttributeStatement` will have access +to the root and `engineering` subdomains: + +``` + + + www + + + engineering + + +``` + + ## Apache-based SSO with `REMOTE_USER` If you have any existing SSO solution where a preferred way to deploy diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index 6f6340ee46..f7de55bb2d 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -127,6 +127,102 @@ AUTHENTICATION_BACKENDS: Tuple[str, ...] = ( # 'zproject.backends.ZulipRemoteUserBackend', # Local SSO, setup docs on readthedocs ) +# LDAP integration. +# +# Zulip supports retrieving information about users via LDAP, and +# optionally using LDAP as an authentication mechanism. + +import ldap +from django_auth_ldap.config import LDAPSearch + +# Connecting to the LDAP server. +# +# For detailed instructions, see the Zulip documentation: +# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap + +# The LDAP server to connect to. Setting this enables Zulip +# automatically fetching each new user's name from LDAP. +# Example: "ldaps://ldap.example.com" +AUTH_LDAP_SERVER_URI = "" + +# The DN of the user to bind as (i.e., authenticate as) in order to +# query LDAP. If unset, Zulip does an anonymous bind. +AUTH_LDAP_BIND_DN = "" + +# Passwords and secrets are not stored in this file. The password +# corresponding to AUTH_LDAP_BIND_DN goes in `/etc/zulip/zulip-secrets.conf`. +# In that file, set `auth_ldap_bind_password`. For example: +# auth_ldap_bind_password = abcd1234 + +# Mapping user info from LDAP to Zulip. +# +# For detailed instructions, see the Zulip documentation: +# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap + +# The LDAP search query to find a given user. +# +# The arguments to `LDAPSearch` are (base DN, scope, filter). In the +# filter, the string `%(user)s` is a Python placeholder. The Zulip +# server will replace this with the user's Zulip username, i.e. the +# name they type into the Zulip login form. +# +# For more details and alternatives, see the documentation linked above. +AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", + ldap.SCOPE_SUBTREE, "(uid=%(user)s)") + +# Configuration to lookup a user's LDAP data given their email address +# (For Zulip reverse mapping). If users log in as e.g. "sam" when +# their email address is "sam@example.com", set LDAP_APPEND_DOMAIN to +# "example.com". Otherwise, leave LDAP_APPEND_DOMAIN=None and set +# AUTH_LDAP_REVERSE_EMAIL_SEARCH and AUTH_LDAP_USERNAME_ATTR below. +#LDAP_APPEND_DOMAIN = None + +# LDAP attribute to find a user's email address. +# +# Leave as None if users log in with their email addresses, +# or if using LDAP_APPEND_DOMAIN. +#LDAP_EMAIL_ATTR = None + +# AUTH_LDAP_REVERSE_EMAIL_SEARCH works like AUTH_LDAP_USER_SEARCH and +# should query an LDAP user given their email address. It and +# AUTH_LDAP_USERNAME_ATTR are required when LDAP_APPEND_DOMAIN is None. +#AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", +# ldap.SCOPE_SUBTREE, "(email=%(email)s)") + +# AUTH_LDAP_USERNAME_ATTR should be the Zulip username attribute +# (defined in AUTH_LDAP_USER_SEARCH). +#AUTH_LDAP_USERNAME_ATTR = "uid" + +# This map defines how to populate attributes of a Zulip user from LDAP. +# +# The format is `zulip_name: ldap_name`; each entry maps a Zulip +# concept (on the left) to the LDAP attribute name (on the right) your +# LDAP database uses for the same concept. +AUTH_LDAP_USER_ATTR_MAP = { + # full_name is required; common values include "cn" or "displayName". + # If names are encoded in your LDAP directory as first and last + # name, you can instead specify first_name and last_name, and + # Zulip will combine those to construct a full_name automatically. + "full_name": "cn", + # "first_name": "fn", + # "last_name": "ln", + + # Profile pictures can be pulled from the LDAP "thumbnailPhoto"/"jpegPhoto" field. + # "avatar": "thumbnailPhoto", + + # This line is for having Zulip to automatically deactivate users + # who are disabled in LDAP/Active Directory (and reactivate users who are not). + # See docs for usage details and precise semantics. + # "userAccountControl": "userAccountControl", +} + +# Whether to automatically deactivate users not found in LDAP. If LDAP +# is the only authentication method, then this setting defaults to +# True. If other authentication methods are enabled, it defaults to +# False. +#LDAP_DEACTIVATE_NON_MATCHING_USERS = True + + ######## # Google OAuth. # @@ -543,106 +639,6 @@ EMAIL_GATEWAY_IMAP_PORT = 993 EMAIL_GATEWAY_IMAP_FOLDER = "INBOX" -################ -# LDAP integration. -# -# Zulip supports retrieving information about users via LDAP, and -# optionally using LDAP as an authentication mechanism. - -import ldap -from django_auth_ldap.config import LDAPSearch - -######## -# LDAP integration, part 1: Connecting to the LDAP server. -# -# For detailed instructions, see the Zulip documentation: -# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap - -# The LDAP server to connect to. Setting this enables Zulip -# automatically fetching each new user's name from LDAP. -# Example: "ldaps://ldap.example.com" -AUTH_LDAP_SERVER_URI = "" - -# The DN of the user to bind as (i.e., authenticate as) in order to -# query LDAP. If unset, Zulip does an anonymous bind. -AUTH_LDAP_BIND_DN = "" - -# Passwords and secrets are not stored in this file. The password -# corresponding to AUTH_LDAP_BIND_DN goes in `/etc/zulip/zulip-secrets.conf`. -# In that file, set `auth_ldap_bind_password`. For example: -# auth_ldap_bind_password = abcd1234 - - -######## -# LDAP integration, part 2: Mapping user info from LDAP to Zulip. -# -# For detailed instructions, see the Zulip documentation: -# https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap - -# The LDAP search query to find a given user. -# -# The arguments to `LDAPSearch` are (base DN, scope, filter). In the -# filter, the string `%(user)s` is a Python placeholder. The Zulip -# server will replace this with the user's Zulip username, i.e. the -# name they type into the Zulip login form. -# -# For more details and alternatives, see the documentation linked above. -AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", - ldap.SCOPE_SUBTREE, "(uid=%(user)s)") - -# Configuration to lookup a user's LDAP data given their email address -# (For Zulip reverse mapping). If users log in as e.g. "sam" when -# their email address is "sam@example.com", set LDAP_APPEND_DOMAIN to -# "example.com". Otherwise, leave LDAP_APPEND_DOMAIN=None and set -# AUTH_LDAP_REVERSE_EMAIL_SEARCH and AUTH_LDAP_USERNAME_ATTR below. -#LDAP_APPEND_DOMAIN = None - -# LDAP attribute to find a user's email address. -# -# Leave as None if users log in with their email addresses, -# or if using LDAP_APPEND_DOMAIN. -#LDAP_EMAIL_ATTR = None - -# AUTH_LDAP_REVERSE_EMAIL_SEARCH works like AUTH_LDAP_USER_SEARCH and -# should query an LDAP user given their email address. It and -# AUTH_LDAP_USERNAME_ATTR are required when LDAP_APPEND_DOMAIN is None. -#AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", -# ldap.SCOPE_SUBTREE, "(email=%(email)s)") - -# AUTH_LDAP_USERNAME_ATTR should be the Zulip username attribute -# (defined in AUTH_LDAP_USER_SEARCH). -#AUTH_LDAP_USERNAME_ATTR = "uid" - -# This map defines how to populate attributes of a Zulip user from LDAP. -# -# The format is `zulip_name: ldap_name`; each entry maps a Zulip -# concept (on the left) to the LDAP attribute name (on the right) your -# LDAP database uses for the same concept. -AUTH_LDAP_USER_ATTR_MAP = { - # full_name is required; common values include "cn" or "displayName". - # If names are encoded in your LDAP directory as first and last - # name, you can instead specify first_name and last_name, and - # Zulip will combine those to construct a full_name automatically. - "full_name": "cn", - # "first_name": "fn", - # "last_name": "ln", - - # Profile pictures can be pulled from the LDAP "thumbnailPhoto"/"jpegPhoto" field. - # "avatar": "thumbnailPhoto", - - # This line is for having Zulip to automatically deactivate users - # who are disabled in LDAP/Active Directory (and reactivate users who are not). - # See docs for usage details and precise semantics. - # "userAccountControl": "userAccountControl", -} - -# Whether to automatically deactivate users not found in LDAP. If LDAP -# is the only authentication method, then this setting defaults to -# True. If other authentication methods are enabled, it defaults to -# False. -#LDAP_DEACTIVATE_NON_MATCHING_USERS = True - - ################ # Video call integrations. #