mirror of
https://github.com/ansible/awx.git
synced 2026-04-09 03:59:21 -02:30
feat: AAP-48498 RADIUS authenticator migrator (#7013)
* feat: AAP-48498 Radius authenticator migrator Issue: AAP-48498 * fix: Namingm Style and tests * enabled by default * test: SECRET is now ignored unless --force is set
This commit is contained in:
committed by
thedoubl3j
parent
ab9bde3698
commit
abc4692231
@@ -7,6 +7,7 @@ from awx.sso.utils.github_migrator import GitHubMigrator
|
|||||||
from awx.sso.utils.ldap_migrator import LDAPMigrator
|
from awx.sso.utils.ldap_migrator import LDAPMigrator
|
||||||
from awx.sso.utils.oidc_migrator import OIDCMigrator
|
from awx.sso.utils.oidc_migrator import OIDCMigrator
|
||||||
from awx.sso.utils.saml_migrator import SAMLMigrator
|
from awx.sso.utils.saml_migrator import SAMLMigrator
|
||||||
|
from awx.sso.utils.radius_migrator import RADIUSMigrator
|
||||||
from awx.main.utils.gateway_client import GatewayClient, GatewayAPIError
|
from awx.main.utils.gateway_client import GatewayClient, GatewayAPIError
|
||||||
|
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class Command(BaseCommand):
|
|||||||
parser.add_argument('--skip-ldap', action='store_true', help='Skip importing LDAP authenticators')
|
parser.add_argument('--skip-ldap', action='store_true', help='Skip importing LDAP authenticators')
|
||||||
parser.add_argument('--skip-ad', action='store_true', help='Skip importing Azure AD authenticator')
|
parser.add_argument('--skip-ad', action='store_true', help='Skip importing Azure AD authenticator')
|
||||||
parser.add_argument('--skip-saml', action='store_true', help='Skip importing SAML authenticator')
|
parser.add_argument('--skip-saml', action='store_true', help='Skip importing SAML authenticator')
|
||||||
|
parser.add_argument('--skip-radius', action='store_true', help='Skip importing RADIUS authenticator')
|
||||||
parser.add_argument('--force', action='store_true', help='Force migration even if configurations already exist')
|
parser.add_argument('--force', action='store_true', help='Force migration even if configurations already exist')
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
@@ -31,6 +33,7 @@ class Command(BaseCommand):
|
|||||||
skip_ldap = options['skip_ldap']
|
skip_ldap = options['skip_ldap']
|
||||||
skip_ad = options['skip_ad']
|
skip_ad = options['skip_ad']
|
||||||
skip_saml = options['skip_saml']
|
skip_saml = options['skip_saml']
|
||||||
|
skip_radius = options['skip_radius']
|
||||||
force = options['force']
|
force = options['force']
|
||||||
|
|
||||||
# If the management command isn't called with all parameters needed to talk to Gateway, consider
|
# If the management command isn't called with all parameters needed to talk to Gateway, consider
|
||||||
@@ -71,6 +74,9 @@ class Command(BaseCommand):
|
|||||||
if not skip_ldap:
|
if not skip_ldap:
|
||||||
migrators.append(LDAPMigrator(gateway_client, self, force=force))
|
migrators.append(LDAPMigrator(gateway_client, self, force=force))
|
||||||
|
|
||||||
|
if not skip_radius:
|
||||||
|
migrators.append(RADIUSMigrator(gateway_client, self, force=force))
|
||||||
|
|
||||||
# Run migrations
|
# Run migrations
|
||||||
total_results = {
|
total_results = {
|
||||||
'created': 0,
|
'created': 0,
|
||||||
|
|||||||
@@ -247,12 +247,12 @@ class TestAuthenticatorConfigComparison:
|
|||||||
match, differences = self.migrator._authenticator_configs_match(existing_auth, new_config, ignore_keys)
|
match, differences = self.migrator._authenticator_configs_match(existing_auth, new_config, ignore_keys)
|
||||||
|
|
||||||
assert match is False
|
assert match is False
|
||||||
assert len(differences) == 3 # KEY, SECRET, NEW_FIELD
|
assert len(differences) == 2 # KEY, NEW_FIELD (SECRET shows up only if --force is used)
|
||||||
|
|
||||||
# Check that all expected differences are captured
|
# Check that all expected differences are captured
|
||||||
difference_text = ' '.join(differences)
|
difference_text = ' '.join(differences)
|
||||||
assert 'KEY:' in difference_text
|
assert 'KEY:' in difference_text
|
||||||
assert 'SECRET:' in difference_text
|
# assert 'SECRET:' in difference_text # SECRET shows up only if --force is used
|
||||||
assert 'NEW_FIELD:' in difference_text
|
assert 'NEW_FIELD:' in difference_text
|
||||||
assert 'CALLBACK_URL' not in difference_text # Should be ignored
|
assert 'CALLBACK_URL' not in difference_text # Should be ignored
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ def existing_tacacsplus_user():
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_radius_config(settings):
|
||||||
|
settings.RADIUS_SERVER = '127.0.0.1'
|
||||||
|
settings.RADIUS_PORT = 1812
|
||||||
|
settings.RADIUS_SECRET = 'secret'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_saml_config(settings):
|
def test_saml_config(settings):
|
||||||
settings.SAML_SECURITY_CONFIG = {
|
settings.SAML_SECURITY_CONFIG = {
|
||||||
|
|||||||
17
awx/sso/tests/unit/test_radius_migrator.py
Normal file
17
awx/sso/tests/unit/test_radius_migrator.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from awx.sso.utils.radius_migrator import RADIUSMigrator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_get_controller_config(test_radius_config):
|
||||||
|
gateway_client = MagicMock()
|
||||||
|
command_obj = MagicMock()
|
||||||
|
obj = RADIUSMigrator(gateway_client, command_obj)
|
||||||
|
|
||||||
|
result = obj.get_controller_config()
|
||||||
|
config = result[0]['settings']['configuration']
|
||||||
|
assert config['SERVER'] == '127.0.0.1'
|
||||||
|
assert config['PORT'] == 1812
|
||||||
|
assert config['SECRET'] == 'secret'
|
||||||
|
assert len(config) == 3
|
||||||
86
awx/sso/utils/radius_migrator.py
Normal file
86
awx/sso/utils/radius_migrator.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
RADIUS authenticator migrator.
|
||||||
|
|
||||||
|
This module handles the migration of RADIUS authenticators from AWX to Gateway.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from awx.sso.utils.base_migrator import BaseAuthenticatorMigrator
|
||||||
|
|
||||||
|
|
||||||
|
class RADIUSMigrator(BaseAuthenticatorMigrator):
|
||||||
|
"""
|
||||||
|
Handles the migration of RADIUS authenticators from AWX to Gateway.
|
||||||
|
"""
|
||||||
|
|
||||||
|
CATEGORY = "RADIUS"
|
||||||
|
AUTH_TYPE = "ansible_base.authentication.authenticator_plugins.radius"
|
||||||
|
|
||||||
|
def get_authenticator_type(self):
|
||||||
|
"""Get the human-readable authenticator type name."""
|
||||||
|
return "RADIUS"
|
||||||
|
|
||||||
|
def get_controller_config(self):
|
||||||
|
"""
|
||||||
|
Export RADIUS authenticators. A RADIUS authenticator is only exported if
|
||||||
|
required configuration is present.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of configured RADIUS authentication providers with their settings
|
||||||
|
"""
|
||||||
|
server = getattr(settings, "RADIUS_SERVER", None)
|
||||||
|
if not server:
|
||||||
|
return []
|
||||||
|
|
||||||
|
port = getattr(settings, "RADIUS_PORT", 1812)
|
||||||
|
secret = getattr(settings, "RADIUS_SECRET", "")
|
||||||
|
|
||||||
|
config_data = {
|
||||||
|
"name": "default",
|
||||||
|
"type": self.AUTH_TYPE,
|
||||||
|
"enabled": True,
|
||||||
|
"create_objects": True,
|
||||||
|
"remove_users": False,
|
||||||
|
"configuration": {
|
||||||
|
"SERVER": server,
|
||||||
|
"PORT": port,
|
||||||
|
"SECRET": secret,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"category": self.CATEGORY,
|
||||||
|
"settings": config_data,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_gateway_authenticator(self, config):
|
||||||
|
"""Create a RADIUS authenticator in Gateway."""
|
||||||
|
category = config["category"]
|
||||||
|
config_settings = config["settings"]
|
||||||
|
name = config_settings["name"]
|
||||||
|
|
||||||
|
# Generate authenticator name and slug
|
||||||
|
authenticator_name = f"AWX-{category.replace('-', '_').title()}-{name}"
|
||||||
|
authenticator_slug = self._generate_authenticator_slug("radius", category)
|
||||||
|
|
||||||
|
self._write_output(f"\n--- Processing {category} authenticator ---")
|
||||||
|
self._write_output(f"Name: {authenticator_name}")
|
||||||
|
self._write_output(f"Slug: {authenticator_slug}")
|
||||||
|
self._write_output(f"Type: {config_settings['type']}")
|
||||||
|
|
||||||
|
# Build Gateway authenticator configuration
|
||||||
|
gateway_config = {
|
||||||
|
"name": authenticator_name,
|
||||||
|
"slug": authenticator_slug,
|
||||||
|
"type": config_settings["type"],
|
||||||
|
"enabled": config_settings["enabled"],
|
||||||
|
"create_objects": config_settings["create_objects"],
|
||||||
|
"remove_users": config_settings["remove_users"],
|
||||||
|
"configuration": config_settings["configuration"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Submit the authenticator (create or update as needed)
|
||||||
|
return self.submit_authenticator(gateway_config, config=config)
|
||||||
Reference in New Issue
Block a user