mirror of
https://github.com/ansible/awx.git
synced 2026-07-12 00:44:36 -02:30
Remove RADIUS authentication (#15548)
Remove RADIUS authentication from AWX Do not remove models fields and tables let it for a stage where all the work of removing external auth finished AAP-27707 Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com>
This commit is contained in:
committed by
jessicamack
parent
e4c11561cc
commit
2c2694ce89
@@ -78,7 +78,7 @@ register(
|
||||
label=_('Allow External Users to Create OAuth2 Tokens'),
|
||||
help_text=_(
|
||||
'For security reasons, users from external auth providers (LDAP, SAML, '
|
||||
'SSO, Radius, and others) are not allowed to create OAuth2 tokens. '
|
||||
'SSO, and others) are not allowed to create OAuth2 tokens. '
|
||||
'To change this behavior, enable this setting. Existing tokens will '
|
||||
'not be deleted when this setting is toggled off.'
|
||||
),
|
||||
|
||||
23
awx/conf/migrations/0011_remove_radius_auth_conf.py
Normal file
23
awx/conf/migrations/0011_remove_radius_auth_conf.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from django.db import migrations
|
||||
|
||||
RADIUS_AUTH_CONF_KEYS = [
|
||||
'RADIUS_SERVER',
|
||||
'RADIUS_PORT',
|
||||
'RADIUS_SECRET',
|
||||
]
|
||||
|
||||
|
||||
def remove_radius_auth_conf(apps, scheme_editor):
|
||||
setting = apps.get_model('conf', 'Setting')
|
||||
setting.objects.filter(key__in=RADIUS_AUTH_CONF_KEYS).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('conf', '0010_change_to_JSONField'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_radius_auth_conf),
|
||||
]
|
||||
@@ -246,11 +246,9 @@ User.add_to_class('is_system_auditor', user_is_system_auditor)
|
||||
|
||||
def user_is_in_enterprise_category(user, category):
|
||||
ret = (category,) in user.enterprise_auth.values_list('provider') and not user.has_usable_password()
|
||||
# NOTE: this if-else block ensures existing enterprise users are still able to
|
||||
# NOTE: this if block ensures existing enterprise users are still able to
|
||||
# log in. Remove it in a future release
|
||||
if category == 'radius':
|
||||
ret = ret or not user.has_usable_password()
|
||||
elif category == 'saml':
|
||||
if category == 'saml':
|
||||
ret = ret or user.social_auth.all()
|
||||
return ret
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ from rest_framework.reverse import reverse as drf_reverse
|
||||
from awx.main.utils.encryption import decrypt_value, get_encryption_key
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models.oauth import OAuth2Application as Application, OAuth2AccessToken as AccessToken
|
||||
from awx.main.tests.functional import immediate_on_commit
|
||||
from awx.sso.models import UserEnterpriseAuth
|
||||
from oauth2_provider.models import RefreshToken
|
||||
|
||||
|
||||
@@ -33,52 +31,6 @@ def test_personal_access_token_creation(oauth_application, post, alice):
|
||||
assert 'refresh_token' in resp_json
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('allow_oauth, status', [(True, 201), (False, 403)])
|
||||
def test_token_creation_disabled_for_external_accounts(oauth_application, post, alice, allow_oauth, status):
|
||||
UserEnterpriseAuth(user=alice, provider='radius').save()
|
||||
url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
|
||||
|
||||
with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USERS=allow_oauth):
|
||||
resp = post(
|
||||
url,
|
||||
data='grant_type=password&username=alice&password=alice&scope=read',
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
|
||||
status=status,
|
||||
)
|
||||
if allow_oauth:
|
||||
assert AccessToken.objects.count() == 1
|
||||
else:
|
||||
assert 'OAuth2 Tokens cannot be created by users associated with an external authentication provider' in smart_str(resp.content) # noqa
|
||||
assert AccessToken.objects.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_existing_token_enabled_for_external_accounts(oauth_application, get, post, admin):
|
||||
UserEnterpriseAuth(user=admin, provider='radius').save()
|
||||
url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
|
||||
with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USERS=True):
|
||||
resp = post(
|
||||
url,
|
||||
data='grant_type=password&username=admin&password=admin&scope=read',
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
HTTP_AUTHORIZATION='Basic ' + smart_str(base64.b64encode(smart_bytes(':'.join([oauth_application.client_id, oauth_application.client_secret])))),
|
||||
status=201,
|
||||
)
|
||||
token = json.loads(resp.content)['access_token']
|
||||
assert AccessToken.objects.count() == 1
|
||||
|
||||
with immediate_on_commit():
|
||||
resp = get(drf_reverse('api:user_me_list', kwargs={'version': 'v2'}), HTTP_AUTHORIZATION='Bearer ' + token, status=200)
|
||||
assert json.loads(resp.content)['results'][0]['username'] == 'admin'
|
||||
|
||||
with override_settings(RADIUS_SERVER='example.org', ALLOW_OAUTH2_FOR_EXTERNAL_USER=False):
|
||||
with immediate_on_commit():
|
||||
resp = get(drf_reverse('api:user_me_list', kwargs={'version': 'v2'}), HTTP_AUTHORIZATION='Bearer ' + token, status=200)
|
||||
assert json.loads(resp.content)['results'][0]['username'] == 'admin'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_pat_creation_no_default_scope(oauth_application, post, admin):
|
||||
# tests that the default scope is overriden
|
||||
|
||||
@@ -7,7 +7,6 @@ import pytest
|
||||
|
||||
# AWX
|
||||
from awx.api.versioning import reverse
|
||||
from awx.conf.models import Setting
|
||||
from awx.conf.registry import settings_registry
|
||||
|
||||
TEST_GIF_LOGO = 'data:image/gif;base64,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' # NOQA
|
||||
@@ -66,38 +65,6 @@ def test_awx_task_env_validity(get, patch, admin, value, expected):
|
||||
assert resp.data['AWX_TASK_ENV'] == dict()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_radius_settings(get, put, patch, delete, admin, settings):
|
||||
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'radius'})
|
||||
response = get(url, user=admin, expect=200)
|
||||
put(url, user=admin, data=response.data, expect=200)
|
||||
# Set secret via the API.
|
||||
patch(url, user=admin, data={'RADIUS_SECRET': 'mysecret'}, expect=200)
|
||||
response = get(url, user=admin, expect=200)
|
||||
assert response.data['RADIUS_SECRET'] == '$encrypted$'
|
||||
assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
|
||||
assert settings.RADIUS_SECRET == 'mysecret'
|
||||
# Set secret via settings wrapper.
|
||||
settings_wrapper = settings._awx_conf_settings
|
||||
settings_wrapper.RADIUS_SECRET = 'mysecret2'
|
||||
response = get(url, user=admin, expect=200)
|
||||
assert response.data['RADIUS_SECRET'] == '$encrypted$'
|
||||
assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
|
||||
assert settings.RADIUS_SECRET == 'mysecret2'
|
||||
# If we send back $encrypted$, the setting is not updated.
|
||||
patch(url, user=admin, data={'RADIUS_SECRET': '$encrypted$'}, expect=200)
|
||||
response = get(url, user=admin, expect=200)
|
||||
assert response.data['RADIUS_SECRET'] == '$encrypted$'
|
||||
assert Setting.objects.filter(key='RADIUS_SECRET').first().value.startswith('$encrypted$')
|
||||
assert settings.RADIUS_SECRET == 'mysecret2'
|
||||
# If we send an empty string, the setting is also set to an empty string.
|
||||
patch(url, user=admin, data={'RADIUS_SECRET': ''}, expect=200)
|
||||
response = get(url, user=admin, expect=200)
|
||||
assert response.data['RADIUS_SECRET'] == ''
|
||||
assert Setting.objects.filter(key='RADIUS_SECRET').first().value == ''
|
||||
assert settings.RADIUS_SECRET == ''
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ui_settings(get, put, patch, delete, admin):
|
||||
url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'ui'})
|
||||
|
||||
@@ -392,7 +392,6 @@ REST_FRAMEWORK = {
|
||||
}
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'awx.sso.backends.RADIUSBackend',
|
||||
'social_core.backends.google.GoogleOAuth2',
|
||||
'social_core.backends.github.GithubOAuth2',
|
||||
'social_core.backends.github.GithubOrganizationOAuth2',
|
||||
@@ -417,12 +416,6 @@ OAUTH2_PROVIDER = {'ACCESS_TOKEN_EXPIRE_SECONDS': 31536000000, 'AUTHORIZATION_CO
|
||||
ALLOW_OAUTH2_FOR_EXTERNAL_USERS = False
|
||||
|
||||
|
||||
# Radius server settings (default to empty string to skip using Radius auth).
|
||||
# Note: These settings may be overridden by database settings.
|
||||
RADIUS_SERVER = ''
|
||||
RADIUS_PORT = 1812
|
||||
RADIUS_SECRET = ''
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -7,11 +7,6 @@ import logging
|
||||
# Django
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings as django_settings
|
||||
from django.utils.encoding import force_str
|
||||
from django.http import HttpResponse
|
||||
|
||||
# radiusauth
|
||||
from radiusauth.backends import RADIUSBackend as BaseRADIUSBackend
|
||||
|
||||
# social
|
||||
from social_core.backends.saml import OID_USERID
|
||||
@@ -45,27 +40,6 @@ def _get_or_set_enterprise_user(username, password, provider):
|
||||
logger.warning("Enterprise user %s already defined in Tower." % username)
|
||||
|
||||
|
||||
class RADIUSBackend(BaseRADIUSBackend):
|
||||
"""
|
||||
Custom Radius backend to verify license status
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username, password):
|
||||
if not django_settings.RADIUS_SERVER:
|
||||
return None
|
||||
return super(RADIUSBackend, self).authenticate(request, username, password)
|
||||
|
||||
def get_user(self, user_id):
|
||||
if not django_settings.RADIUS_SERVER:
|
||||
return None
|
||||
user = super(RADIUSBackend, self).get_user(user_id)
|
||||
if not user.has_usable_password():
|
||||
return user
|
||||
|
||||
def get_django_user(self, username, password=None, groups=[], is_staff=False, is_superuser=False):
|
||||
return _get_or_set_enterprise_user(force_str(username), force_str(password), 'radius')
|
||||
|
||||
|
||||
class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider):
|
||||
"""
|
||||
Custom Identity Provider to make attributes to what we expect.
|
||||
|
||||
@@ -188,7 +188,6 @@ def is_remote_auth_enabled():
|
||||
|
||||
settings_that_turn_on_remote_auth = [
|
||||
'SOCIAL_AUTH_SAML_ENABLED_IDPS',
|
||||
'RADIUS_SERVER',
|
||||
]
|
||||
# Also include any SOCAIL_AUTH_*KEY (except SAML)
|
||||
for social_auth_key in dir(settings):
|
||||
|
||||
@@ -143,46 +143,6 @@ if settings.ALLOW_LOCAL_RESOURCE_MANAGEMENT:
|
||||
category_slug='authentication',
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# RADIUS AUTHENTICATION SETTINGS
|
||||
###############################################################################
|
||||
|
||||
register(
|
||||
'RADIUS_SERVER',
|
||||
field_class=fields.CharField,
|
||||
allow_blank=True,
|
||||
default='',
|
||||
label=_('RADIUS Server'),
|
||||
help_text=_('Hostname/IP of RADIUS server. RADIUS authentication is disabled if this setting is empty.'),
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
placeholder='radius.example.com',
|
||||
)
|
||||
|
||||
register(
|
||||
'RADIUS_PORT',
|
||||
field_class=fields.IntegerField,
|
||||
min_value=1,
|
||||
max_value=65535,
|
||||
default=1812,
|
||||
label=_('RADIUS Port'),
|
||||
help_text=_('Port of RADIUS server.'),
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
)
|
||||
|
||||
register(
|
||||
'RADIUS_SECRET',
|
||||
field_class=fields.CharField,
|
||||
allow_blank=True,
|
||||
default='',
|
||||
label=_('RADIUS Secret'),
|
||||
help_text=_('Shared secret for authenticating to RADIUS server.'),
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# GOOGLE OAUTH2 AUTHENTICATION SETTINGS
|
||||
###############################################################################
|
||||
|
||||
@@ -107,7 +107,6 @@ class AuthenticationBackendsField(fields.StringListField):
|
||||
# authentication backend.
|
||||
REQUIRED_BACKEND_SETTINGS = collections.OrderedDict(
|
||||
[
|
||||
('awx.sso.backends.RADIUSBackend', ['RADIUS_SERVER']),
|
||||
('social_core.backends.google.GoogleOAuth2', ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET']),
|
||||
('social_core.backends.github.GithubOAuth2', ['SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_SECRET']),
|
||||
('social_core.backends.open_id_connect.OpenIdConnectAuth', ['SOCIAL_AUTH_OIDC_KEY', 'SOCIAL_AUTH_OIDC_SECRET', 'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT']),
|
||||
|
||||
@@ -335,7 +335,6 @@ class TestCommonFunctions:
|
||||
# Set none of the social auth settings
|
||||
('JUNK_SETTING', False),
|
||||
('SOCIAL_AUTH_SAML_ENABLED_IDPS', True),
|
||||
('RADIUS_SERVER', True),
|
||||
# Set some SOCIAL_SOCIAL_AUTH_OIDC_KEYAUTH_*_KEY settings
|
||||
('SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', True),
|
||||
('SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY', True),
|
||||
|
||||
Reference in New Issue
Block a user