Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove TACACS+ authentication #15547

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -388,7 +388,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 @@ -419,15 +418,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
Loading