Skip to content

Commit

Permalink
Remove TACACS+ authentication (#15547)
Browse files Browse the repository at this point in the history
Remove TACACS+ authentication from AWX.

Co-authored-by: Hao Liu <[email protected]>
  • Loading branch information
ldjebran and TheRealHaoLiu committed Oct 15, 2024
1 parent 9892379 commit 1e0170c
Show file tree
Hide file tree
Showing 27 changed files with 31 additions and 571 deletions.
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ GRAFANA ?= false
VAULT ?= false
# If set to true docker-compose will also start a hashicorp vault instance with TLS enabled
VAULT_TLS ?= false
# If set to true docker-compose will also start a tacacs+ instance
TACACS ?= false
# If set to true docker-compose will also start an OpenTelemetry Collector instance
OTEL ?= false
# If set to true docker-compose will also start a Loki instance
Expand Down Expand Up @@ -511,7 +509,6 @@ docker-compose-sources: .git/hooks/pre-commit
-e enable_grafana=$(GRAFANA) \
-e enable_vault=$(VAULT) \
-e vault_tls=$(VAULT_TLS) \
-e enable_tacacs=$(TACACS) \
-e enable_otel=$(OTEL) \
-e enable_loki=$(LOKI) \
-e install_editable_dependencies=$(EDITABLE_DEPENDENCIES) \
Expand Down
26 changes: 26 additions & 0 deletions awx/conf/migrations/0011_remove_tacacs_plus_auth_conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import migrations

TACACS_PLUS_AUTH_CONF_KEYS = [
'TACACSPLUS_HOST',
'TACACSPLUS_PORT',
'TACACSPLUS_SECRET',
'TACACSPLUS_SESSION_TIMEOUT',
'TACACSPLUS_AUTH_PROTOCOL',
'TACACSPLUS_REM_ADDR',
]


def remove_tacacs_plus_auth_conf(apps, scheme_editor):
setting = apps.get_model('conf', 'Setting')
setting.objects.filter(key__in=TACACS_PLUS_AUTH_CONF_KEYS).delete()


class Migration(migrations.Migration):

dependencies = [
('conf', '0010_change_to_JSONField'),
]

operations = [
migrations.RunPython(remove_tacacs_plus_auth_conf),
]
15 changes: 0 additions & 15 deletions awx/main/tests/functional/api/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,6 @@ def test_radius_settings(get, put, patch, delete, admin, settings):
assert settings.RADIUS_SECRET == ''


@pytest.mark.django_db
def test_tacacsplus_settings(get, put, patch, admin):
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'tacacsplus'})
response = get(url, user=admin, expect=200)
put(url, user=admin, data=response.data, expect=200)
patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
patch(url, user=admin, data={'TACACSPLUS_SECRET': ''}, expect=200)
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=400)
patch(url, user=admin, data={'TACACSPLUS_SECRET': 'mysecret'}, expect=200)
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost'}, expect=200)
patch(url, user=admin, data={'TACACSPLUS_HOST': '', 'TACACSPLUS_SECRET': ''}, expect=200)
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': ''}, expect=400)
patch(url, user=admin, data={'TACACSPLUS_HOST': 'localhost', 'TACACSPLUS_SECRET': 'mysecret'}, expect=200)


@pytest.mark.django_db
def test_ui_settings(get, put, patch, delete, admin):
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'})
Expand Down
10 changes: 0 additions & 10 deletions awx/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@

AUTHENTICATION_BACKENDS = (
'awx.sso.backends.RADIUSBackend',
'awx.sso.backends.TACACSPlusBackend',
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.github.GithubOAuth2',
'social_core.backends.github.GithubOrganizationOAuth2',
Expand Down Expand Up @@ -420,15 +419,6 @@
RADIUS_PORT = 1812
RADIUS_SECRET = ''

# TACACS+ settings (default host to empty string to skip using TACACS+ auth).
# Note: These settings may be overridden by database settings.
TACACSPLUS_HOST = ''
TACACSPLUS_PORT = 49
TACACSPLUS_SECRET = ''
TACACSPLUS_SESSION_TIMEOUT = 5
TACACSPLUS_AUTH_PROTOCOL = 'ascii'
TACACSPLUS_REM_ADDR = False

# Enable / Disable HTTP Basic Authentication used in the API browser
# Note: Session limits are not enforced when using HTTP Basic Authentication.
# Note: This setting may be overridden by database settings.
Expand Down
51 changes: 0 additions & 51 deletions awx/sso/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
# radiusauth
from radiusauth.backends import RADIUSBackend as BaseRADIUSBackend

# tacacs+ auth
import tacacs_plus

# social
from social_core.backends.saml import OID_USERID
from social_core.backends.saml import SAMLAuth as BaseSAMLAuth
Expand Down Expand Up @@ -69,54 +66,6 @@ def get_django_user(self, username, password=None, groups=[], is_staff=False, is
return _get_or_set_enterprise_user(force_str(username), force_str(password), 'radius')


class TACACSPlusBackend(object):
"""
Custom TACACS+ auth backend for AWX
"""

def authenticate(self, request, username, password):
if not django_settings.TACACSPLUS_HOST:
return None
try:
# Upstream TACACS+ client does not accept non-string, so convert if needed.
tacacs_client = tacacs_plus.TACACSClient(
django_settings.TACACSPLUS_HOST,
django_settings.TACACSPLUS_PORT,
django_settings.TACACSPLUS_SECRET,
timeout=django_settings.TACACSPLUS_SESSION_TIMEOUT,
)
auth_kwargs = {'authen_type': tacacs_plus.TAC_PLUS_AUTHEN_TYPES[django_settings.TACACSPLUS_AUTH_PROTOCOL]}
if django_settings.TACACSPLUS_AUTH_PROTOCOL:
client_ip = self._get_client_ip(request)
if client_ip:
auth_kwargs['rem_addr'] = client_ip
auth = tacacs_client.authenticate(username, password, **auth_kwargs)
except Exception as e:
logger.exception("TACACS+ Authentication Error: %s" % str(e))
return None
if auth.valid:
return _get_or_set_enterprise_user(username, password, 'tacacs+')

def get_user(self, user_id):
if not django_settings.TACACSPLUS_HOST:
return None
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None

def _get_client_ip(self, request):
if not request or not hasattr(request, 'META'):
return None

x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip


class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider):
"""
Custom Identity Provider to make attributes to what we expect.
Expand Down
2 changes: 0 additions & 2 deletions awx/sso/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,9 @@ def get_external_account(user):
def is_remote_auth_enabled():
from django.conf import settings

# Append Radius, TACACS+ and SAML options
settings_that_turn_on_remote_auth = [
'SOCIAL_AUTH_SAML_ENABLED_IDPS',
'RADIUS_SERVER',
'TACACSPLUS_HOST',
]
# Also include any SOCAIL_AUTH_*KEY (except SAML)
for social_auth_key in dir(settings):
Expand Down
97 changes: 1 addition & 96 deletions awx/sso/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

# Django REST Framework
from rest_framework import serializers

# AWX
from awx.conf import register, register_validate, fields
from awx.conf import register, fields
from awx.sso.fields import (
AuthenticationBackendsField,
SAMLContactField,
Expand All @@ -25,7 +22,6 @@
SocialTeamMapField,
)
from awx.main.validators import validate_private_key, validate_certificate
from awx.sso.validators import validate_tacacsplus_disallow_nonascii # noqa


class SocialAuthCallbackURL(object):
Expand Down Expand Up @@ -187,79 +183,6 @@ def __call__(self):
encrypted=True,
)

###############################################################################
# TACACSPLUS AUTHENTICATION SETTINGS
###############################################################################

register(
'TACACSPLUS_HOST',
field_class=fields.CharField,
allow_blank=True,
default='',
label=_('TACACS+ Server'),
help_text=_('Hostname of TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
)

register(
'TACACSPLUS_PORT',
field_class=fields.IntegerField,
min_value=1,
max_value=65535,
default=49,
label=_('TACACS+ Port'),
help_text=_('Port number of TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
)

register(
'TACACSPLUS_SECRET',
field_class=fields.CharField,
allow_blank=True,
default='',
validators=[validate_tacacsplus_disallow_nonascii],
label=_('TACACS+ Secret'),
help_text=_('Shared secret for authenticating to TACACS+ server.'),
category=_('TACACS+'),
category_slug='tacacsplus',
encrypted=True,
)

register(
'TACACSPLUS_SESSION_TIMEOUT',
field_class=fields.IntegerField,
min_value=0,
default=5,
label=_('TACACS+ Auth Session Timeout'),
help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'),
category=_('TACACS+'),
category_slug='tacacsplus',
unit=_('seconds'),
)

register(
'TACACSPLUS_AUTH_PROTOCOL',
field_class=fields.ChoiceField,
choices=['ascii', 'pap'],
default='ascii',
label=_('TACACS+ Authentication Protocol'),
help_text=_('Choose the authentication protocol used by TACACS+ client.'),
category=_('TACACS+'),
category_slug='tacacsplus',
)

register(
'TACACSPLUS_REM_ADDR',
field_class=fields.BooleanField,
default=True,
label=_('TACACS+ client address sending enabled'),
help_text=_('Enable the client address sending by TACACS+ client.'),
category=_('TACACS+'),
category_slug='tacacsplus',
)

###############################################################################
# GOOGLE OAUTH2 AUTHENTICATION SETTINGS
###############################################################################
Expand Down Expand Up @@ -1344,21 +1267,3 @@ def get_saml_entity_id():
category=_('Authentication'),
category_slug='authentication',
)

def tacacs_validate(serializer, attrs):
if not serializer.instance or not hasattr(serializer.instance, 'TACACSPLUS_HOST') or not hasattr(serializer.instance, 'TACACSPLUS_SECRET'):
return attrs
errors = []
host = serializer.instance.TACACSPLUS_HOST
if 'TACACSPLUS_HOST' in attrs:
host = attrs['TACACSPLUS_HOST']
secret = serializer.instance.TACACSPLUS_SECRET
if 'TACACSPLUS_SECRET' in attrs:
secret = attrs['TACACSPLUS_SECRET']
if host and not secret:
errors.append('TACACSPLUS_SECRET is required when TACACSPLUS_HOST is provided.')
if errors:
raise serializers.ValidationError(_('\n'.join(errors)))
return attrs

register_validate('tacacsplus', tacacs_validate)
1 change: 0 additions & 1 deletion awx/sso/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# AWX
from awx.conf import fields
from awx.main.validators import validate_certificate
from awx.sso.validators import validate_tacacsplus_disallow_nonascii # noqa


def get_subclasses(cls):
Expand Down
1 change: 1 addition & 0 deletions awx/sso/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.utils.translation import gettext_lazy as _


# todo: this model to be removed as part of sso removal issue AAP-28380
class UserEnterpriseAuth(models.Model):
"""Enterprise Auth association model"""

Expand Down
34 changes: 0 additions & 34 deletions awx/sso/tests/conftest.py

This file was deleted.

3 changes: 1 addition & 2 deletions awx/sso/tests/functional/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def test_get_external_account(self, enable_social, enable_enterprise, expected_r
if enable_enterprise:
from awx.sso.models import UserEnterpriseAuth

enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+')
enterprise_auth = UserEnterpriseAuth(user=user, provider='saml')
enterprise_auth.save()

assert get_external_account(user) == expected_results
Expand All @@ -336,7 +336,6 @@ def test_get_external_account(self, enable_social, enable_enterprise, expected_r
('JUNK_SETTING', False),
('SOCIAL_AUTH_SAML_ENABLED_IDPS', True),
('RADIUS_SERVER', True),
('TACACSPLUS_HOST', True),
# Set some SOCIAL_SOCIAL_AUTH_OIDC_KEYAUTH_*_KEY settings
('SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', True),
('SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', True),
Expand Down
37 changes: 0 additions & 37 deletions awx/sso/tests/functional/test_get_or_set_enterprise_user.py

This file was deleted.

Loading

0 comments on commit 1e0170c

Please sign in to comment.