cvat
cvat copied to clipboard
Add instruction to enable LDAP auth
I see LDAP only in user_guide.md
Probably it is related to django-auth-ldap
Hi @lupin-de-mid ,
create settings.py file and add content below. Replace all <...>
on something which is specific for your LDAP server configuration. Read django_auth_ldap documentation for more details about different parameters.
Inside docker-compose.override.yml you need to define DJANGO_SETTINGS_MODULE: settings
inside environment section for cvat container. It will replace default cvat.settings.production
settings file.
from cvat.settings.production import *
# add custom apps here
import ldap
from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType
DJANGO_AUTH_TYPE = 'LDAP'
AUTH_LOGIN_NOTE = '''<p>
For successful login please make sure you are member of cvat_users group
</p>'''
# Baseline configuration.
AUTH_LDAP_SERVER_URI = "ldap://<ldap-host>:<ldap-port>"
# Credentials for LDAP server
AUTH_LDAP_BIND_DN = "<username>"
AUTH_LDAP_BIND_PASSWORD = "<password>"
# Set up basic user search
AUTH_LDAP_USER_SEARCH = LDAPSearch("<params>",
ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)")
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("<params>",
ldap.SCOPE_SUBTREE, "(objectClass=group)")
AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType()
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_AUTHORIZE_ALL_USERS = True
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
AUTH_LDAP_ADMIN_GROUPS = [
'CN=cvat_admin,<params>',
]
AUTH_LDAP_ANNOTATOR_GROUPS = [
'CN=data_annotation,<params>',
]
AUTH_LDAP_USER_GROUPS = [
'CN=cvat_users,<params>',
]
AUTH_LDAP_OBSERVER_GROUPS = [
'CN=cvat_users,<params>',
]
@lupin-de-mid , let me know if it doesn't work for you. Could you please help us and contribute some documentation based on your experience and my hints?
Hi @nmanovic, i'll try this week
Is it nesessary to provide groups in ldap?
I prefer manage permissions by cvat
itself and use LDAP only for AUTH
Hi @nmanovic, i'll try this week
Is it nesessary to provide groups in ldap? I prefer manage permissions by
cvat
itself and use LDAP only for AUTH
It is necessary because it is how CVAT will determine if the user has admin, user, annotator or observer roles. In case of basic authorization you do it manually in admin
panel. In case of LDAP, admin
panel can not be used anymore for specifying roles (you can but such settings will be reseted after LDAP cache is reinitialized) as far as I remember.
I used a very similar configuration to the one presented above and I confirm it's working
Where should I put the settings.py file to be picked up by build via docker-compose?
I used a very similar configuration to the one presented above and I confirm it's working @rcbop Can you describe the detail ?
I install CVAT through helm, how should I configure LDAP?
Hi @lupin-de-mid ,
create settings.py file and add content below. Replace all
<...>
on something which is specific for your LDAP server configuration. Read django_auth_ldap documentation for more details about different parameters.Inside docker-compose.override.yml you need to define
DJANGO_SETTINGS_MODULE: settings
inside environment section for cvat container. It will replace defaultcvat.settings.production
settings file.from cvat.settings.production import * # add custom apps here import ldap from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType DJANGO_AUTH_TYPE = 'LDAP' AUTH_LOGIN_NOTE = '''<p> For successful login please make sure you are member of cvat_users group </p>''' # Baseline configuration. AUTH_LDAP_SERVER_URI = "ldap://<ldap-host>:<ldap-port>" # Credentials for LDAP server AUTH_LDAP_BIND_DN = "<username>" AUTH_LDAP_BIND_PASSWORD = "<password>" # Set up basic user search AUTH_LDAP_USER_SEARCH = LDAPSearch("<params>", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)") # Set up the basic group parameters. AUTH_LDAP_GROUP_SEARCH = LDAPSearch("<params>", ldap.SCOPE_SUBTREE, "(objectClass=group)") AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType() # Populate the Django user from the LDAP directory. AUTH_LDAP_USER_ATTR_MAP = { "first_name": "givenName", "last_name": "sn", "email": "mail", } AUTH_LDAP_ALWAYS_UPDATE_USER = True # Cache group memberships for an hour to minimize LDAP traffic AUTH_LDAP_CACHE_GROUPS = True AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600 AUTH_LDAP_AUTHORIZE_ALL_USERS = True # Keep ModelBackend around for per-user permissions and maybe a local # superuser. AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend'] AUTH_LDAP_ADMIN_GROUPS = [ 'CN=cvat_admin,<params>', ] AUTH_LDAP_ANNOTATOR_GROUPS = [ 'CN=data_annotation,<params>', ] AUTH_LDAP_USER_GROUPS = [ 'CN=cvat_users,<params>', ] AUTH_LDAP_OBSERVER_GROUPS = [ 'CN=cvat_users,<params>', ]
this file in settings.py? and it's path is /home/django/settings.py in container?
from cvat.settings.production import *
I put the settings.py file in the container's /home/django/ directory.it cannot log in with an LDAP account. Any other places to configure?
Ok this information is pretty far out of date. Configuration settings, environment variables and flow have all changed from 2.x. Here is what I did to get it to work.
We need override several settings in the ./cvat/settings/base.py
that isn't listed here.
Make a docker-compose.override.yml
in your ./cvat/
folder (where your docker-compose.yml file is).
version: '3.3'
services:
cvat:
environment:
DJANGO_SETTINGS_MODULE: settings
volumes:
- ./settings.py:/home/django/settings.py:ro
Create a settings.py
file in your ./cvat
folder (same directory as above)
Key things we need to override in settings.py
are
IAM_TYPE = 'LDAP'
and
DJANGO_AUTH_LDAP_GROUPS
Here is my full working settings.py file (with my LDAP services redacted)
from cvat.settings.production import *
# add custom apps here
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType, NestedActiveDirectoryGroupType
IAM_TYPE = 'LDAP'
AUTH_LOGIN_NOTE = '''<p>
For successful login please make sure you are member of cvat_users group
</p>'''
# Baseline configuration.
AUTH_LDAP_SERVER_URI = "<redacted>"
# Credentials for LDAP server
AUTH_LDAP_BIND_DN = "<redacted>"
AUTH_LDAP_BIND_PASSWORD = "<redacted>"
# Set up basic user search
AUTH_LDAP_USER_SEARCH = LDAPSearch("<redacted>",
ldap.SCOPE_SUBTREE, "(uid=%(user)s)")
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("<redacted>",
ldap.SCOPE_SUBTREE, "(objectClass=groupofnames)")
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_AUTHORIZE_ALL_USERS = False
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
# example 'cn=cvat_admin,cn=groups,cn=accounts,dc=example,dc=com'
# change your cn to match whatever groups you have in your LDAP
AUTH_LDAP_ADMIN_GROUPS = [
'cn=<redacted>,
]
AUTH_LDAP_WORKER_GROUPS = [
'cn=<redacted>,
]
AUTH_LDAP_USER_GROUPS = [
'cn=<redacted>,
]
AUTH_LDAP_BUSINESS_GROUPS = [
'cn=<redacted>,
]
DJANGO_AUTH_LDAP_GROUPS = {"admin": AUTH_LDAP_ADMIN_GROUPS, "business": AUTH_LDAP_WORKER_GROUPS, "user": AUTH_LDAP_USER_GROUPS, "worker":AUTH_LDAP_BUSINESS_GROUPS}
Three notable changes IAM_TYPE = 'LDAP'
, AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
(I'm using FreeIPA so we have to use GroupOfNames), and DJANGO_AUTH_LDAP_GROUPS = {"admin": AUTH_LDAP_ADMIN_GROUPS, "business": AUTH_LDAP_WORKER_GROUPS, "user": AUTH_LDAP_USER_GROUPS, "worker":AUTH_LDAP_BUSINESS_GROUPS}
.
DJANGO_AUTH_LDAP_GROUPS admin
, business
, user
, worker
is coming from IAM_ROLES = [IAM_ADMIN_ROLE, 'business', 'user', 'worker']
in ./cvat/settings/base.py
in the code it "matches" based on the keywords in IAM_ROLES
. The AUTH_LDAP_ADMIN_GROUPS
, AUTH_LDAP_WORKER_GROUPS
, AUTH_LDAP_USER_GROUPS
, AUTH_LDAP_BUSINESS_GROUPS
need to be added to DJANGO_AUTH_LDAP_GROUPS
list depending on your groups. If you want to add more groups, just add more in your settings.py file. Hopefully this helps someone else for v2.x+. This works for me for CVAT v2.1.
@vaskokj
Hello,
The writeup you gave has been invaluable to me getting authentication with Active Directory, but I am unable to get group mappings to work. Are you sure DJANGO_AUTH_LDAP_GROUPS
is valid? I cannot find any reference to this variable on google other than this exact issue.
I am able to authenticate, but users with the proper group assigned in the domain do not get elevated in CVAT.
Attached I have snippet of how I have groups set up
AUTH_LDAP_ADMIN_GROUPS = [
'cn=Domain Admins,cn=Users,dc=ad.dc=example,dc=com',
]
AUTH_LDAP_BUSINESS_GROUPS = [
'cn=Domain Admins,cn=Users,dc=ad,dc=example,dc=com',
]
AUTH_LDAP_USER_GROUPS = [
'cn=Users,dc=ad,dc=example,dc=com',
]
AUTH_LDAP_WORKER_GROUPS = [
'cn=Users,dc=ad,dc=example,dc=com',
]
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
"business": AUTH_LDAP_BUSINESS_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"worker": AUTH_LDAP_WORKER_GROUPS,
}
The assumption is that Domain Admins are elevated to admin in CVAT but this does not appear to be the case.
@RyanHir Here is a less redacted version of that section.
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
AUTH_LDAP_ADMIN_GROUPS = [
'CN=cvat_admin,cn=groups,cn=accounts,dc=example,dc=com',
]
AUTH_LDAP_WORKER_GROUPS = [
'CN=cvat_worker,cn=groups,cn=accounts,dc=example,dc=com',
]
AUTH_LDAP_USER_GROUPS = [
'CN=cvat_users,cn=groups,cn=accounts,dc=examples,dc=com',
]
AUTH_LDAP_BUSINESS_GROUPS = [
'CN=cvat_business,cn=groups,cn=accounts,dc=example,dc=com',
]
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
"worker": AUTH_LDAP_WORKER_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"business":AUTH_LDAP_BUSINESS_GROUPS
}
One other thing to check would be the group search section. You might be failing to find your groups depending on where you are searching.
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("cn=groups,cn=accounts,dc=example,dc=com",
ldap.SCOPE_SUBTREE, "(objectClass=groupofnames)")
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
I did end up having to change AUTH_LDAP_GROUP_TYPE. Since you are using AD you might need https://django-auth-ldap.readthedocs.io/en/latest/reference.html#django_auth_ldap.config.ActiveDirectoryGroupType. See the list of options here from django. https://django-auth-ldap.readthedocs.io/en/latest/reference.html#django_auth_ldap.config.ActiveDirectoryGroupType
Also double check your SCOPE_SUBTREE. Running a query on the users and groups and getting all of their options should give you want you need to search for in this config.
I will note that the permissions that this enables is extremely vague. I never could find a good clear understanding of what permissions each one of these items gives you.
Also make sure you clear the CVAT users. If I remember correctly, CVAT only reads the groups on CVAT user creation.
Hopefully this helps.
@vaskokj I have recreated a config as close to what you have with FreeIPA, but I am still unable to get CVAT group assignment to work. I even deleted all volumes to ensure CVAT accounts are being recreated. ldapsearch
is showing that the user is correctly assigned to be a member of cn=cvat_admins,cn=groups,cn=accounts,dc=ipa,dc=example,dc=com
.
# We are overlaying production
from cvat.settings.production import *
# Custom code below
import ldap
from django_auth_ldap.config import LDAPSearch
from django_auth_ldap.config import GroupOfNamesType
# Notify CVAT that we are using LDAP authentication
DJANGO_AUTH_TYPE = 'LDAP'
# Talking to the LDAP server
AUTH_LDAP_SERVER_URI = "ldap://ipa.example.com" # IP Addresses also work
ldap.set_option(ldap.OPT_REFERRALS, 0)
# Authenticating with the LDAP server
AUTH_LDAP_BIND_DN = "CN=cvat_bind,CN=Users,CN=Accounts,DC=ipa,DC=example,DC=com"
AUTH_LDAP_BIND_PASSWORD = "SuperSecurePassword^21"
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"CN=Users,CN=Accounts,DC=ipa,DC=example,DC=com",
ldap.SCOPE_SUBTREE,
"(uid=%(user)s)"
)
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"CN=Groups,CN=Accounts,DC=ipa,DC=example,DC=com",
ldap.SCOPE_SUBTREE,
"(objectClass=groupOfNames)"
)
# Mapping Django field names to FreeIPA attributes
AUTH_LDAP_USER_ATTR_MAP = {
"user_name": "uid",
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}
# Group Management
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Register Django LDAP backend
AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
# Map FreeIPA groups to Django/CVAT groups.
AUTH_LDAP_ADMIN_GROUPS = [
'CN=cvat_admins,CN=Groups,CN=Accounts,DC=ipa,DC=example,DC=com',
]
AUTH_LDAP_BUSINESS_GROUPS = [
'CN=cvat_managers,CN=Groups,CN=Accounts,DC=ipa,DC=example,DC=com',
]
AUTH_LDAP_WORKER_GROUPS = [
'CN=Groups,CN=Accounts,DC=ipa,DC=example,DC=com',
]
AUTH_LDAP_USER_GROUPS = [
'CN=Groups,CN=Accounts,DC=ipa,DC=example,DC=com',
]
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
"business": AUTH_LDAP_BUSINESS_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"worker": AUTH_LDAP_WORKER_GROUPS,
}
I believe the issue is with CVAT itself or Django. I opened an issue earlier when I was working with Active Directory documentation cvat-ai/cvat#40
@RyanHir
I had the same problem. Users could authenticate but it wasn't pulling the groups properly. Same symptoms, I could have missed something in my config here, but it might be a difference in our environments (e.g. FreeIPA vs AD).
What I did to debug the issue is add log statements into this piece of code https://github.com/openvinotoolkit/cvat/blob/develop/cvat/apps/iam/signals.py#L34 to see what CVAT thought each user was in at that point I was able to see what LDAP was being queried for in the LDAP logs.
@vaskokj
I do not think it is an issue of environments, as I can recreate this on both FreeIPA and AD. I will look into adding logs.
Fair enough. Can you check your cn=Users,dc=ad.dc=example,dc=com
to make sure you are pulling your cn properly? cn being uid vs users vs accounts, etc.?
Whoops, that was a typo. I corrected that earlier, but forgot to put that down. From the best I can tell, I am pulling the correct properties.
I found after putting logs that DJANGO_AUTH_TYPE
needs to be replaced with IAM_TYPE
. And I later discovered that in my config, the group name was incorrect cvat_admin
vs cvat_admins
.
@RyanHir Glad you figured it out. I missed that too in your question but looks like I had it in my comment https://github.com/openvinotoolkit/cvat/issues/311#issuecomment-1146163794. This section of code is really sketch and probably needs to be reevaluated.
Thanks to everyone else in this thread.
In case it helps others I've successfully integrated this with authentik using the below settings.py
:
"""LDAP authentication override for CVAT
See:
* https://github.com/opencv/cvat/issues/311 for helpful information with cvat
* https://version-2024-2.goauthentik.io/docs/providers/ldap/ for canonical authentik reference
* https://django-auth-ldap.readthedocs.io/en/latest/authentication.html for Django LDAP docs
"""
import os
from cvat.settings.production import *
# add custom apps here
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType, NestedActiveDirectoryGroupType
# ========================
# AUTHENTIK
#
# You can find this info from the `LDAP Provider` page in authentik once you have created it
# ========================
BASE_DN: str = os.environ.get("BASE_DN","ou=cvat,dc=ldap,dc=goauthentik,dc=io") # also called `Search base` in authentik
USERS_DN: str = os.environ.get("USERS_DN",f"ou=users,{BASE_DN}")
GROUPS_DN: str = os.environ.get("GROUPS_DN",f"ou=groups,{BASE_DN}")
AUTH_LDAP_BIND_DN: str = os.environ.get("BIND_DN",f"cn=akadmin,{USERS_DN}") # Credentials for LDAP server
AUTH_LDAP_BIND_PASSWORD: str = os.environ.get("AUTH_LDAP_BIND_PASSWORD") # Credentials for LDAP server
if not AUTH_LDAP_BIND_PASSWORD:
raise Exception("AUTH_LDAP_BIND_PASSWORD environment variable not found.")
# ========================
IAM_TYPE = 'LDAP'
AUTH_LOGIN_NOTE = '''<p>
For successful login please make sure you are member of cvat_users group
</p>'''
# Baseline configuration.
AUTH_LDAP_SERVER_URI = os.environ.get('AUTH_LDAP_SERVER_URI', 'ldap://authentik_ldap:3389')
if not AUTH_LDAP_SERVER_URI:
raise Exception("AUTH_LDAP_SERVER_URI environment variable not found.")
ldap.set_option(ldap.OPT_REFERRALS, 0) # disable referrals
# Set up basic user search
AUTH_LDAP_USER_SEARCH = LDAPSearch(USERS_DN,
ldap.SCOPE_SUBTREE, "(cn=%(user)s)")
# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(GROUPS_DN,
ldap.SCOPE_SUBTREE, "(objectClass=groupofnames)")
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"user_name": "cn",
"first_name": "cn",
"last_name": "sn",
"email": "mail",
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True
# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
AUTH_LDAP_AUTHORIZE_ALL_USERS = False
# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS += ['django_auth_ldap.backend.LDAPBackend']
# example 'cn=cvat_admin,cn=groups,cn=accounts,dc=example,dc=com'
# change your cn to match whatever groups you have in your LDAP
AUTH_LDAP_ADMIN_GROUPS = [
f'cn=cvat_admins,{GROUPS_DN}'
]
AUTH_LDAP_WORKER_GROUPS = [
f'cn=cvat_workers,{GROUPS_DN}'
]
AUTH_LDAP_USER_GROUPS = [
f'cn=cvat_users,{GROUPS_DN}'
]
AUTH_LDAP_BUSINESS_GROUPS = [
f'cn=cvat_managers,{GROUPS_DN}'
]
DJANGO_AUTH_LDAP_GROUPS = {
"admin": AUTH_LDAP_ADMIN_GROUPS,
"business": AUTH_LDAP_WORKER_GROUPS,
"user": AUTH_LDAP_USER_GROUPS,
"worker":AUTH_LDAP_BUSINESS_GROUPS
}
The docker-compose-ldap.override.yaml
I used on top of the basic one provided in the repository (note the external ldap
network that my authentik outpost is connected to). Note you'll need to use a .env
file to pass through the necessary environment variables to the settings.py
above.
You'll run this with a command similar to docker-compose -f cvat/docker-compose.yml -f docker-compose-dev.override.yaml -f docker-compose-ldap.override.yaml up
(where I'm using both some development environment overrides as well as the LDAP one finally).
# file: docker-compose-ldap.override.yaml
services:
cvat_server:
env_file: .env
environment:
DJANGO_SETTINGS_MODULE: settings
volumes:
- ../settings.py:/home/django/settings.py:ro
networks:
ldap:
networks:
ldap:
name: ldap
external: true
And then finally the additional LDAP service I needed to add in my authentik docker-compose file.
# file: authentik/docker-compose.yaml
services:
...
# LDAP Outpost
authentik_ldap:
env_file: .env
image: ghcr.io/goauthentik/ldap
hostname: ldap.${ROOT_DOMAIN}
# Optionally specify which networks the container should be
# might be needed to reach the core authentik server
networks:
- default
- ldap
ports:
- 389 # 389:3389 | LDAP server
- 636 # 636:6636 | LDAP SSL server
- 9300 # Metrics server
environment:
AUTHENTIK_HOST: http://auth.${ROOT_DOMAIN}
AUTHENTIK_INSECURE: "true"
AUTHENTIK_TOKEN: ${LDAP_TOKEN}
AUTHENTIK_DEBUG: true # unset/false in production