Soren B
Soren B

Reputation: 73

'AnonymousUserMixin' object has no attribute 'roles' error in Airflow Webserver when using LDAP with Flask-AppBuilder RBAC

I'm trying to integrate Airflow Webserver authentication with the Flask-AppBuilder RBAC available in Airflow 1.10.0, but no matter the configuration settings I try, I get an AttributeError: 'AnonymousUserMixin' object has no attribute 'roles'.

Previously I had gotten the Airflow LDAP auth backend to work with my org's LDAP Microsoft AD server, but I can't get the configuration settings right to enable it to work with FAB RBAC. The Airflow and FAB documentations have very little to say about LDAP or troubleshooting it.

In the ${AIRFLOW_HOME}/webserver_config.py file I have

# The authentication type
AUTH_TYPE = AUTH_LDAP

AUTH_ROLE_PUBLIC = "Public"
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"

AUTH_LDAP_SERVER = "ldaps://ldaps.myorg.org:636"
AUTH_LDAP_BIND_USER = "CN=myuser,OU=Service Accounts,DC=myorg,DC=org"
AUTH_LDAP_BIND_PASSWORD = "relevant_password"
AUTH_LDAP_SEARCH = "DC=myorg,DC=org"
AUTH_LDAP_UID_FIELD = "sAMAccountName"
# AUTH_LDAP_ALLOW_SELF_SIGNED = True
AUTH_LDAP_USE_TLS = False
AUTH_LDAP_APPEND_DOMAIN = "myorg.org"
AUTH_ROLE_ADMIN = "Admin"

And in {AIRFLOW_HOME}/airflow.cfg I have

[webserver]
authenticate = True
rbac = True

I have already upgraded the backend Postgres DB so that it has the ab_ tables.

When I deploy everything via Docker Swarm and go to the appropriate Webserver URL, I just get the error listed above with the stack trace. It doesn't ever give me the option to log in (and I tried clearing the cookie), so I don't understand how to get it to let an 'anonymous' user even try to authenticate against the LDAP AD.

Is the issue

Please let me know if I've left any information out. Thank you!

Upvotes: 1

Views: 3301

Answers (2)

Soren B
Soren B

Reputation: 73

Well, luckily I figured it out and am able to answer my own question, the first one that has been answered on SO so far.

Part of the reason is that FAB uses the python-ldap package for error handling, but there are also the ldap3 and ldap packages, which if you installed in a particular order could interfere with the use of python-ldap.

I also found that python-ldap has certain build requirements.

Additionally, I discovered that FAB has another LDAP parameter, AUTH_LDAP_SEARCH_FILTER, that lets you filter possible users to a certain group, which is exactly what I wanted, but it's not mentioned in the documentation; I found it in the package repo on GitHub.

Here's my webserver_config.py file that works for me:

# -*- coding: utf-8 -*-

import os
from airflow import configuration as conf
from flask_appbuilder.security.manager import AUTH_LDAP
basedir = os.path.abspath(os.path.dirname(__file__))

# The SQLAlchemy connection string.
SQLALCHEMY_DATABASE_URI = conf.get('core', 'SQL_ALCHEMY_CONN')

# Flask-WTF flag for CSRF
CSRF_ENABLED = True

# ------------------------------------------------------------------------------
# AUTHENTICATION CONFIG
# ------------------------------------------------------------------------------
# For details on how to set up each of the following authentications, see
# http://flask-appbuilder.readthedocs.io/en/latest/security.html# authentication-methods

# The authentication type
AUTH_TYPE = AUTH_LDAP

AUTH_ROLE_PUBLIC = "Public"
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Viewer"

AUTH_LDAP_SERVER = "ldaps://ldaps.myorg.org:636"
AUTH_LDAP_BIND_USER = "CN=myuser,OU=Service Accounts,DC=myorg,DC=org"
AUTH_LDAP_BIND_PASSWORD = "relevant_password"
AUTH_LDAP_SEARCH = "DC=myorg,DC=org"  # same as BASEDN
AUTH_LDAP_SEARCH_FILTER = "(memberOf=CN=My Team,OU=My Group,DC=myorg,DC=org)"
AUTH_LDAP_UID_FIELD = AUTH_LDAP_UID_FIELD = "sAMAccountName"
AUTH_LDAP_ALLOW_SELF_SIGNED = True
AUTH_LDAP_USE_TLS = False
AUTH_LDAP_TLS_DEMAND = True
AUTH_ROLE_ADMIN = "Admin"

I set the AUTH_USER_REGISTRATION_ROLE to Viewer because by default Public has no permissions associated with it, so if someone in AD logs in and their role were defaulted to Public, they wouldn't be able to do anything until an Admin changed their role.

I learned from IT at my org that the domain controller of the LDAP server handles the TLS, so I think that's why I can set AUTH_LDAP_ALLOW_SELF_SIGNED to True, whereas I don't have a TLS cert in the Docker containers to point Airflow to in order to set AUTH_LDAP_USE_TLS to True.

Upvotes: 2

Bhav Bhela
Bhav Bhela

Reputation: 327

AnonymousUserMixin is a mixin class defined in the Flask-Login python package. You're likely experiencing an issue where a codepath is assuming a logged in user. You can change that codepath to be as follows:

if flask_login.current_user.is_authenticated():
    *code*

You may want to consider adding a custom class as your anonymous user class in your Flask app configuration/setup code.

class MyCustomAnonymousUser(flask_login.AnonymousUserMixin):
    def __init__(self):
        self.roles = []

flask_app = Flask(...)  # some flask app being initialized
flask_app.login_manager.anonymous_user = MyCustomAnonymousUser

However, it's also possible the code that's throwing this error is expecting Flask-Security to be configured. A peek under the hood of what Flask-Security does:

from flask.ext.login import AnonymousUserMixin, UserMixin as BaseUserMixin, \
LoginManager, current_user

...


class AnonymousUser(AnonymousUserMixin):
    """AnonymousUser definition"""

    def __init__(self):
        self.roles = ImmutableList()

    def has_role(self, *args):
        """Returns `False`"""
        return False

To configure Flask-Security for your app, check out the quick start on their documentation: https://pythonhosted.org/Flask-Security/quickstart.html

Upvotes: 0

Related Questions