Geonode auth via LDAP¶
Create demo LDAP¶
In order to test GeoNode support for LDAP authentication over the next section we’ll create a simple local LDAP server.
# change machine hostname
sudo hostnamectl set-hostname ldap.example.com
sudo vim /etc/hosts
192.168.18.50 ldap.example.com
sudo apt -y install slapd ldap-utils
# NOTE: You will be prompted to provide slapd an admin account password
# check if slapd install is ok
sudo slapcat
# You should receive a similar content response
> dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: example.com
dc: example
structuralObjectClass: organization
entryUUID: c183e400-c205-103b-9ba4-0b759e265ed4
creatorsName: cn=admin,dc=example,dc=com
createTimestamp: 20211015131547Z
entryCSN: 20211015131547.819933Z#000000#000#000000
modifiersName: cn=admin,dc=example,dc=com
modifyTimestamp: 20211015131547Z
dn: cn=admin,dc=example,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9RlBEQmdmcEJiL2ZnQkUzeGJRb2hObG5GczdmN0VIQmY=
structuralObjectClass: organizationalRole
entryUUID: c186ea1a-c205-103b-9ba5-0b759e265ed4
creatorsName: cn=admin,dc=example,dc=com
createTimestamp: 20211015131547Z
entryCSN: 20211015131547.839808Z#000000#000#000000
modifiersName: cn=admin,dc=example,dc=com
modifyTimestamp: 20211015131547Z
# Create base geonode users ldap file
mkdir -p /opt/geonode/ldap
cd /opt/geonode/ldap
vim geonodedn.ldif
# Create the following content and save file after
dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
# add an admin user to our ldap (use the ldap password on install step)
sudo ldapadd -x -D cn=admin,dc=example,dc=com -W -f geonodedn.ldif
# generate a password for the user account to add
slappasswd
New password:
Re-enter new password:
{SSHA}u0Qj+PWagxB5sV7z0X0MQLXleLARzo8k
# create a ldif file for geonode users
vim ldapusers.ldif
# add a similar content
# replace <my-user> with your username
dn: uid=<my-user>,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: geonode
sn: Wiz
userPassword: {SSHA}u0Qj+PWagxB5sV7z0X0MQLXleLARzo8k
loginShell: /bin/bash
uidNumber: 2000
gidNumber: 2000
homeDirectory: /home/<my-user>
# create the ldap user
sudo ldapadd -x -D cn=admin,dc=example,dc=com -W -f ldapusers.ldif
# create a ldap group
vim ldapgroups.ldif
# add the following content
dn: cn=geonode,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: geonode
gidNumber: 2000
memberUid: geonode
# create the geonode ldap group
ldapadd -x -D cn=admin,dc=example,dc=com -W -f ldapgroups.ldif
This package provides utilities for using LDAP as an authentication and authorization backend for geonode.
The django_auth_ldap package is a very capable way to add LDAP integration with django projects. It provides a lot of flexibility in mapping LDAP users to geonode users and is able to manage user authentication.
However, in order to provide full support for mapping LDAP groups with geonode’s and enforce group permissions on resources, a custom geonode authentication backend is required. This contrib package provides such a backend, based on django_auth_ldap.
Installation¶
Installing this contrib package is a matter of:
Installing system LDAP libraries (development packages needed)
Cloning this repository locally
Change to the ldap directory and install this contrib package
# install systemwide LDAP libraries sudo apt install libldap2-dev libsasl2-dev # get geonode/contribs code cd /opt git clone https://github.com/GeoNode/geonode-contribs.git # optional: if deactivate our virtualenv activate it again workon geonode # install geonode ldap contrib package cd geonode-contribs/ldap pip install .
Configuration¶
Add
geonode_ldap.backend.GeonodeLdapBackend
as an additional auth backend.
# e.g. by updating your settings.py or local_settings.py AUTHENTICATION_BACKENDS += ( "geonode_ldap.backend.GeonodeLdapBackend", ) # open setting.py or local_settings.py and locate AUTHENTICATION_BACKENDS # add the new backend line as abovecd /opt/geonode/geonode vim settings.py You may use additional auth backends, the django authentication framework tries them all according to the order listed in the settings. This means that geonode can be setup in such a way as to permit internal organization users to login with their LDAP credentials, while at the same time allowing for casual users to use their facebook login (as long as you enable facebook social auth provider). .. note:: The django's ``django.contrib.auth.backends.ModelBackend`` must also be used in order to provide full geonode integration with LDAP. However this is included by default on GeoNode ``settings`` .. code-block:: python # The GeoNode default settings are the following AUTHENTICATION_BACKENDS = ( 'oauth2_provider.backends.OAuth2Backend', 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend', 'allauth.account.auth_backends.AuthenticationBackend', )
Set some additional configuration values. Some of these variables are prefixed with
AUTH_LDAP
(these are used directly by django_auth_ldap) while others are prefixed withGEONODE_LDAP
(these are used bygeonode_ldap
). The geonode custom variables are:GEONODE_LDAP_GROUP_PROFILE_FILTERSTR
- This is an LDAP search fragment with the filter that allows querying for existing groups. See example belowGEONODE_LDAP_GROUP_NAME_ATTRIBUTE
- This is the name of the LDAP attribute that will be used for deriving the geonode group name. If not specified it will default to cn, which means that the LDAP object’s common name will be used for generating the name of the geonode groupGEONODE_LDAP_GROUP_PROFILE_MEMBER_ATTR
- This is the name of the LDAP attribute that will be used for deriving the geonode membership. If not specified it will default tomember
Example configuration:
# add these import lines to the top of your geonode settings file
from django_auth_ldap import config as ldap_config
from geonode_ldap.config import GeonodeNestedGroupOfNamesType
import ldap
# enable logging
import logging
logger = logging.getLogger('django_auth_ldap')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
# add both standard ModelBackend auth and geonode.contrib.ldap auth
AUTHENTICATION_BACKENDS += (
'geonode_ldap.backend.GeonodeLdapBackend',
)
# django_auth_ldap configuration
AUTH_LDAP_SERVER_URI = os.getenv("LDAP_SERVER_URL")
AUTH_LDAP_BIND_DN = os.getenv("LDAP_BIND_DN")
AUTH_LDAP_BIND_PASSWORD = os.getenv("LDAP_BIND_PASSWORD")
AUTH_LDAP_USER_SEARCH = ldap_config.LDAPSearch(
os.getenv("LDAP_USER_SEARCH_DN"),
ldap.SCOPE_SUBTREE,
os.getenv("LDAP_USER_SEARCH_FILTERSTR")
)
# should LDAP groups be used to spawn groups in GeoNode?
AUTH_LDAP_MIRROR_GROUPS = strtobool(os.getenv("LDAP_MIRROR_GROUPS", 'True'))
AUTH_LDAP_GROUP_SEARCH = ldap_config.LDAPSearch(
os.getenv("LDAP_GROUP_SEARCH_DN"),
ldap.SCOPE_SUBTREE,
os.getenv("LDAP_GROUP_SEARCH_FILTERSTR")
)
AUTH_LDAP_GROUP_TYPE = GeonodeNestedGroupOfNamesType()
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mailPrimaryAddress"
}
AUTH_LDAP_FIND_GROUP_PERMS = True
AUTH_LDAP_MIRROR_GROUPS_EXCEPT = [
"test_group"
]
# these are not needed by django_auth_ldap - we use them to find and match
# GroupProfiles and GroupCategories
GEONODE_LDAP_GROUP_NAME_ATTRIBUTE = os.getenv("LDAP_GROUP_NAME_ATTRIBUTE", default="cn")
GEONODE_LDAP_GROUP_PROFILE_FILTERSTR = os.getenv("LDAP_GROUP_SEARCH_FILTERSTR", default='(ou=research group)')
GEONODE_LDAP_GROUP_PROFILE_MEMBER_ATTR = os.getenv("LDAP_GROUP_PROFILE_MEMBER_ATTR", default='member')
Example environment variables: (add these to the previous geonode.ini file created under wgsi installation)
LDAP_SERVER_URL=ldap://<the_ldap_server>
LDAP_BIND_DN=uid=ldapinfo,cn=users,dc=ad,dc=example,dc=org
LDAP_BIND_PASSWORD=<something_secret>
LDAP_USER_SEARCH_DN=dc=ad,dc=example,dc=org
LDAP_USER_SEARCH_FILTERSTR=(&(uid=%(user)s)(objectClass=person))
LDAP_MIRROR_GROUPS=True
LDAP_GROUP_SEARCH_DN=cn=groups,dc=ad,dc=example,dc=org
LDAP_GROUP_SEARCH_FILTERSTR=(|(cn=abt1)(cn=abt2)(cn=abt3)(cn=abt4)(cn=abt5)(cn=abt6))
LDAP_GROUP_PROFILE_MEMBER_ATTR=uniqueMember
The configuration seen in the example above will allow LDAP users to login to geonode with their LDAP credentials.
On first login, a geonode user is created from the LDAP user and its LDAP
attributes cn
and sn
are used to populate the geonode user’s
first_name
and last_name
profile fields.
Any groups that the user is a member of in LDAP (under the
cn=groups,dc=ad,dc=example,dc=org
search base and belonging to one of
(|(cn=abt1)(cn=abt2)(cn=abt3)(cn=abt4)(cn=abt5)(cn=abt6))
groups) will be mapped to the corresponding
geonode groups, even creating these groups in geonode in case they do not
exist yet. The geonode user is also made a member of these geonode groups.
Upon each login, the user’s geonode group memberships are re-evaluated
according to the information extracted from LDAP. The
AUTH_LDAP_MIRROR_GROUPS_EXCEPT
setting can be used to specify groups
whose memberships will not be re-evaluated.
If no LDAP groups shall be mirrored LDAP_MIRROR_GROUPS
and LDAP_MIRROR_GROUPS_EXCEPT
must be set to False
.
Note
Users mapped from LDAP will be marked with an ldap
tag. This will be used to keep them in sync.
Warning
If you remove the ldap
tag, the users will be threaten as pure internal GeoNode ones.
You may also manually generate the geonode groups in advance, before users login. In this case, when a user logs in and the mapped LDAP group already exists, the user is merely added to the geonode group
Be sure to check out django_auth_ldap for more information on the various configuration options.
Keep Users and Groups Synchronized¶
In order to constantly keep the remote LDAP Users and Groups synchronized with GeoNode, you will need to run periodically some specific management commands.
*/10 * * * * /opt/geonode/my-geonode/manage.sh updateldapgroups >> /var/log/cron.log 2>&1
*/10 * * * * /opt/geonode/my-geonode/manage.sh updateldapusers >> /var/log/cron.log 2>&1
Where the manage.sh
is a bash script similar to the following one:
manage.sh
export $(grep -v '^#' /opt/geonode/my-geonode/.env | xargs -d '\n'); /home/<my_user>/.virtualenvs/geonode/bin/python /opt/geonode/my-geonode/manage.py $@
and the /opt/geonode/my-geonode/.env
is something similar to the following one:
/opt/geonode/my-geonode/.env
DEBUG=False
DJANGO_ALLOWED_HOSTS=<geonode_public_host>,localhost,127.0.0.1
DJANGO_DATABASE_URL=postgis://my_geonode:**********@localhost:5432/my_geonode_db
DEFAULT_BACKEND_UPLOADER=geonode.importer
DEFAULT_FROM_EMAIL=geonode@example.org
DJANGO_EMAIL_HOST=smtp.example.org
DJANGO_EMAIL_HOST_PASSWORD=**********
DJANGO_EMAIL_HOST_USER=geonode
DJANGO_EMAIL_PORT=465
DJANGO_EMAIL_USE_SSL=True
DJANGO_SETTINGS_MODULE=my_geonode.settings
DJANGO_SECRET_KEY=**********
OAUTH2_API_KEY=**********
PROXY_URL=/proxy/?url=
EXIF_ENABLED=True
EMAIL_ENABLE=True
TIME_ENABLED=True
ACCOUNT_OPEN_SIGNUP=True
ACCOUNT_APPROVAL_REQUIRED=True
ACCOUNT_EMAIL_REQUIRED=True
ACCOUNT_EMAIL_VERIFICATION=optional
AVATAR_GRAVATAR_SSL=True
GEONODE_DB_URL=postgis://my_geonode:**********@localhost:5432/my_geonode_data
GEOSERVER_ADMIN_PASSWORD=**********
GEOSERVER_LOCATION=https://<geonode_public_host>/geoserver/
GEOSERVER_PUBLIC_HOST=<geonode_public_host>
GEOSERVER_PUBLIC_LOCATION=https://<geonode_public_host>/geoserver/
GEOSERVER_WEB_UI_LOCATION=https://<geonode_public_host>/geoserver/
LDAP_SERVER_URL=ldap://<the_ldap_server>
LDAP_BIND_DN=uid=ldapinfo,cn=users,dc=ad,dc=example,dc=org
LDAP_BIND_PASSWORD=<something_secret>
LDAP_USER_SEARCH_DN=dc=ad,dc=example,dc=org
LDAP_USER_SEARCH_FILTERSTR=(&(uid=%(user)s)(objectClass=person))
LDAP_MIRROR_GROUPS=True
LDAP_GROUP_SEARCH_DN=cn=groups,dc=ad,dc=example,dc=org
LDAP_GROUP_SEARCH_FILTERSTR=(|(cn=abt1)(cn=abt2)(cn=abt3)(cn=abt4)(cn=abt5)(cn=abt6))
LDAP_GROUP_PROFILE_MEMBER_ATTR=uniqueMember
OGC_REQUEST_MAX_RETRIES=3
OGC_REQUEST_POOL_CONNECTIONS=100
OGC_REQUEST_POOL_MAXSIZE=100
OGC_REQUEST_TIMEOUT=60
SITEURL=https://<geonode_public_host>/
SITE_HOST_NAME=<geonode_public_host>
FREETEXT_KEYWORDS_READONLY=False
# Advanced Workflow Settings
ADMIN_MODERATE_UPLOADS=False
GROUP_MANDATORY_RESOURCES=False
GROUP_PRIVATE_RESOURCES=False
RESOURCE_PUBLISHING=False
Note
You might want to use the same /opt/geonode/my-geonode/.env
for your UWSGI
configuration too:
[uwsgi]
socket = 0.0.0.0:8000
uid = <my_user>
gid = www-data
plugins = python3
virtualenv = /home/<my_user>/.virtualenvs/geonode
# set environment variables from .env file
env LANG=en_US.utf8
env LC_ALL=en_US.UTF-8
env LC_LANG=en_US.UTF-8
for-readline = /opt/geonode/my-geonode/.env
env = %(_)
endfor =
chdir = /opt/geonode/my-geonode
module = my_geonode.wsgi:application
processes = 12
threads = 2
enable-threads = true
master = true
# logging
# path to where uwsgi logs will be saved
logto = /storage/my_geonode/logs/geonode.log
daemonize = /storage/my_geonode/logs/geonode.log
touch-reload = /opt/geonode/my-geonode/my_geonode/wsgi.py
buffer-size = 32768
max-requests = 500
harakiri = 300 # respawn processes taking more than 5 minutes (300 seconds)
# limit-as = 1024 # avoid Errno 12 cannot allocate memory
harakiri-verbose = true
vacuum = true
thunder-lock = true