diff --git a/awx/main/migrations/0118_galaxy_credentials.py b/awx/main/migrations/0118_galaxy_credentials.py index c46fd962a7..d47f966fec 100644 --- a/awx/main/migrations/0118_galaxy_credentials.py +++ b/awx/main/migrations/0118_galaxy_credentials.py @@ -9,82 +9,13 @@ from django.db import migrations, models from django.utils.timezone import now import django.db.models.deletion +from awx.main.migrations import _galaxy as galaxy from awx.main.models import CredentialType as ModernCredentialType from awx.main.utils.common import set_current_apps logger = logging.getLogger('awx.main.migrations') -def migrate_galaxy_settings(apps, schema_editor): - set_current_apps(apps) - ModernCredentialType.setup_tower_managed_defaults() - Organization = apps.get_model('main', 'Organization') - CredentialType = apps.get_model('main', 'CredentialType') - Credential = apps.get_model('main', 'Credential') - Setting = apps.get_model('conf', 'Setting') - - galaxy_type = CredentialType.objects.get(kind='galaxy') - private_galaxy_url = Setting.objects.filter(key='PRIMARY_GALAXY_URL').first() - - # by default, prior versions of AWX/Tower automatically pulled content - # from galaxy.ansible.com - public_galaxy_enabled = True - public_galaxy_setting = Setting.objects.filter(key='PUBLIC_GALAXY_ENABLED').first() - if public_galaxy_setting and public_galaxy_setting is False: - # ...UNLESS this behavior was explicitly disabled via this setting - public_galaxy_enabled = False - - for org in Organization.objects.all(): - if private_galaxy_url and private_galaxy_url.value: - # If a setting exists for a private Galaxy URL, make a credential for it - username = Setting.objects.filter(key='PRIMARY_GALAXY_USERNAME').first() - password = Setting.objects.filter(key='PRIMARY_GALAXY_PASSWORD').first() - if (username and username.value) or (password and password.value): - logger.error( - f'Specifying HTTP basic auth for the Ansible Galaxy API ' - f'({private_galaxy_url.value}) is no longer supported. ' - 'Please provide an API token instead after your upgrade ' - 'has completed', - ) - inputs = { - 'url': private_galaxy_url.value - } - token = Setting.objects.filter(key='PRIMARY_GALAXY_TOKEN').first() - if token and token.value: - inputs['token'] = decrypt_field(token, 'value') - auth_url = Setting.objects.filter(key='PRIMARY_GALAXY_AUTH_URL').first() - if auth_url and auth_url.value: - inputs['auth_url'] = auth_url.value - cred = Credential( - created=now(), - modified=now(), - name=f'Private Galaxy ({private_galaxy_url.value})', - organization=org, - credential_type=galaxy_type, - inputs=inputs - ) - cred.save() - if token and token.value: - # encrypt based on the primary key from the prior save - cred.inputs['token'] = encrypt_field(cred, 'token') - cred.save() - org.galaxy_credentials.add(cred) - if public_galaxy_enabled: - # If public Galaxy was enabled, make a credential for it - cred = Credential( - created=now(), - modified=now(), - name='Ansible Galaxy', - organization=org, - credential_type=galaxy_type, - inputs = { - 'url': 'https://galaxy.ansible.com/' - } - ) - cred.save() - org.galaxy_credentials.add(cred) - - class Migration(migrations.Migration): dependencies = [ @@ -111,5 +42,5 @@ class Migration(migrations.Migration): name='galaxy_credentials', field=awx.main.fields.OrderedManyToManyField(blank=True, related_name='organization_galaxy_credentials', through='main.OrganizationGalaxyCredentialMembership', to='main.Credential'), ), - migrations.RunPython(migrate_galaxy_settings) + migrations.RunPython(galaxy.migrate_galaxy_settings) ] diff --git a/awx/main/migrations/_galaxy.py b/awx/main/migrations/_galaxy.py new file mode 100644 index 0000000000..2f6cfc25fd --- /dev/null +++ b/awx/main/migrations/_galaxy.py @@ -0,0 +1,85 @@ +# Generated by Django 2.2.11 on 2020-08-04 15:19 + +import logging + +from awx.main.utils.encryption import encrypt_field, decrypt_field + +from django.utils.timezone import now + +from awx.main.models import CredentialType as ModernCredentialType +from awx.main.utils.common import set_current_apps + +logger = logging.getLogger('awx.main.migrations') + + +def migrate_galaxy_settings(apps, schema_editor): + set_current_apps(apps) + ModernCredentialType.setup_tower_managed_defaults() + Organization = apps.get_model('main', 'Organization') + CredentialType = apps.get_model('main', 'CredentialType') + Credential = apps.get_model('main', 'Credential') + Setting = apps.get_model('conf', 'Setting') + + galaxy_type = CredentialType.objects.get(kind='galaxy') + private_galaxy_url = Setting.objects.filter(key='PRIMARY_GALAXY_URL').first() + + # by default, prior versions of AWX/Tower automatically pulled content + # from galaxy.ansible.com + public_galaxy_enabled = True + public_galaxy_setting = Setting.objects.filter(key='PUBLIC_GALAXY_ENABLED').first() + if public_galaxy_setting and public_galaxy_setting.value is False: + # ...UNLESS this behavior was explicitly disabled via this setting + public_galaxy_enabled = False + + for org in Organization.objects.all(): + if private_galaxy_url and private_galaxy_url.value: + # If a setting exists for a private Galaxy URL, make a credential for it + username = Setting.objects.filter(key='PRIMARY_GALAXY_USERNAME').first() + password = Setting.objects.filter(key='PRIMARY_GALAXY_PASSWORD').first() + if (username and username.value) or (password and password.value): + logger.error( + f'Specifying HTTP basic auth for the Ansible Galaxy API ' + f'({private_galaxy_url.value}) is no longer supported. ' + 'Please provide an API token instead after your upgrade ' + 'has completed', + ) + inputs = { + 'url': private_galaxy_url.value + } + token = Setting.objects.filter(key='PRIMARY_GALAXY_TOKEN').first() + if token and token.value: + inputs['token'] = decrypt_field(token, 'value') + auth_url = Setting.objects.filter(key='PRIMARY_GALAXY_AUTH_URL').first() + if auth_url and auth_url.value: + inputs['auth_url'] = auth_url.value + name = f'Private Galaxy ({private_galaxy_url.value})' + if 'cloud.redhat.com' in inputs['url']: + name = f'Ansible Automation Hub ({private_galaxy_url.value})' + cred = Credential( + created=now(), + modified=now(), + name=name, + organization=org, + credential_type=galaxy_type, + inputs=inputs + ) + cred.save() + if token and token.value: + # encrypt based on the primary key from the prior save + cred.inputs['token'] = encrypt_field(cred, 'token') + cred.save() + org.galaxy_credentials.add(cred) + if public_galaxy_enabled: + # If public Galaxy was enabled, make a credential for it + cred = Credential( + created=now(), + modified=now(), + name='Ansible Galaxy', + organization=org, + credential_type=galaxy_type, + inputs = { + 'url': 'https://galaxy.ansible.com/' + } + ) + cred.save() + org.galaxy_credentials.add(cred) diff --git a/awx/main/redact.py b/awx/main/redact.py index c0e2365941..32899d935e 100644 --- a/awx/main/redact.py +++ b/awx/main/redact.py @@ -10,14 +10,6 @@ class UriCleaner(object): @staticmethod def remove_sensitive(cleartext): - # exclude_list contains the items that will _not_ be redacted - # TODO: replace this with the server URLs from proj.org.credentials - exclude_list = [] - #exclude_list = [settings.PUBLIC_GALAXY_SERVER['url']] - #if settings.PRIMARY_GALAXY_URL: - # exclude_list += [settings.PRIMARY_GALAXY_URL] - #if settings.FALLBACK_GALAXY_SERVERS: - # exclude_list += [server['url'] for server in settings.FALLBACK_GALAXY_SERVERS] redactedtext = cleartext text_index = 0 while True: @@ -25,10 +17,6 @@ class UriCleaner(object): if not match: break uri_str = match.group(1) - # Do not redact items from the exclude list - if any(uri_str.startswith(exclude_uri) for exclude_uri in exclude_list): - text_index = match.start() + len(uri_str) - continue try: # May raise a ValueError if invalid URI for one reason or another o = urlparse.urlsplit(uri_str) diff --git a/awx/main/tests/functional/test_galaxy_credential_migration.py b/awx/main/tests/functional/test_galaxy_credential_migration.py new file mode 100644 index 0000000000..7c1c89c202 --- /dev/null +++ b/awx/main/tests/functional/test_galaxy_credential_migration.py @@ -0,0 +1,78 @@ +import importlib + +from django.contrib.contenttypes.models import ContentType +import pytest + +from awx.main.models import Organization +from awx.conf.models import Setting +from awx.main.migrations import _galaxy as galaxy + + +class FakeApps(object): + def get_model(self, app, model): + if app == 'contenttypes': + return ContentType + return getattr(importlib.import_module(f'awx.{app}.models'), model) + + +apps = FakeApps() + + +@pytest.mark.django_db +def test_default_public_galaxy(): + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 1 + creds = org.galaxy_credentials.all() + assert creds[0].name == 'Ansible Galaxy' + assert creds[0].inputs['url'] == 'https://galaxy.ansible.com/' + + +@pytest.mark.django_db +def test_public_galaxy_disabled(): + Setting.objects.create(key='PUBLIC_GALAXY_ENABLED', value=False) + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 0 + + +@pytest.mark.django_db +def test_rh_automation_hub(): + Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://cloud.redhat.com/api/automation-hub/') + Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123') + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 2 + assert org.galaxy_credentials.first().name == 'Ansible Automation Hub (https://cloud.redhat.com/api/automation-hub/)' # noqa + + +@pytest.mark.django_db +def test_multiple_galaxies(): + for i in range(5): + Organization.objects.create(name=f'Org {i}') + + Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_AUTH_URL', value='https://auth.example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_USERNAME', value='user') + Setting.objects.create(key='PRIMARY_GALAXY_PASSWORD', value='pass') + Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123') + + for org in Organization.objects.all(): + assert org.galaxy_credentials.count() == 0 + + galaxy.migrate_galaxy_settings(apps, None) + + for org in Organization.objects.all(): + assert org.galaxy_credentials.count() == 2 + creds = org.galaxy_credentials.all() + assert creds[0].name == 'Private Galaxy (https://example.org/)' + assert creds[0].inputs['url'] == 'https://example.org/' + assert creds[0].inputs['auth_url'] == 'https://auth.example.org/' + assert creds[0].inputs['token'].startswith('$encrypted$') + assert creds[0].get_input('token') == 'secret123' + + assert creds[1].name == 'Ansible Galaxy' + assert creds[1].inputs['url'] == 'https://galaxy.ansible.com/'