From ede9d961da5c8c7c318e23e8d59b51f841e78328 Mon Sep 17 00:00:00 2001 From: tongtie Date: Tue, 14 Sep 2021 22:20:52 +0800 Subject: [PATCH 01/14] fix: Internationalization causes the project to be unable to choose manual select --- awx/ui/src/screens/Project/shared/ProjectForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/src/screens/Project/shared/ProjectForm.js b/awx/ui/src/screens/Project/shared/ProjectForm.js index af3b21f52d..91239784c3 100644 --- a/awx/ui/src/screens/Project/shared/ProjectForm.js +++ b/awx/ui/src/screens/Project/shared/ProjectForm.js @@ -224,7 +224,7 @@ function ProjectFormFields({ isDisabled: true, }, ...scmTypeOptions.map(([value, label]) => { - if (label === 'Manual') { + if (value === '') { value = 'manual'; } return { From 6d2c10ad02db12539e52219f4836e0c33dc47616 Mon Sep 17 00:00:00 2001 From: Martin Vician Date: Fri, 5 Aug 2022 14:13:12 +0100 Subject: [PATCH 02/14] Added domain item and authorizer for TSS --- awx/main/credential_plugins/tss.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/awx/main/credential_plugins/tss.py b/awx/main/credential_plugins/tss.py index 1803400e2f..44a35b7dbd 100644 --- a/awx/main/credential_plugins/tss.py +++ b/awx/main/credential_plugins/tss.py @@ -1,7 +1,7 @@ from .plugin import CredentialPlugin from django.utils.translation import gettext_lazy as _ -from thycotic.secrets.server import PasswordGrantAuthorizer, SecretServer, ServerSecret +from thycotic.secrets.server import DomainPasswordGrantAuthorizer, PasswordGrantAuthorizer, SecretServer, ServerSecret tss_inputs = { 'fields': [ @@ -17,6 +17,12 @@ tss_inputs = { 'help_text': _('The (Application) user username'), 'type': 'string', }, + { + 'id': 'domain', + 'label': _('Domain'), + 'help_text': _('The (Application) user domain'), + 'type': 'string', + }, { 'id': 'password', 'label': _('Password'), @@ -44,7 +50,10 @@ tss_inputs = { def tss_backend(**kwargs): - authorizer = PasswordGrantAuthorizer(kwargs['server_url'], kwargs['username'], kwargs['password']) + if 'domain' in kwargs: + authorizer = DomainPasswordGrantAuthorizer(kwargs['server_url'], kwargs['username'], kwargs['password'], kwargs['domain']) + else: + authorizer = PasswordGrantAuthorizer(kwargs['server_url'], kwargs['username'], kwargs['password']) secret_server = SecretServer(kwargs['server_url'], authorizer) secret_dict = secret_server.get_secret(kwargs['secret_id']) secret = ServerSecret(**secret_dict) From e53a5da91eca8966422222e3ec46b393b9b560e2 Mon Sep 17 00:00:00 2001 From: Alexander Komarov Date: Fri, 30 Jul 2021 14:21:41 +0500 Subject: [PATCH 03/14] Add more tests for different modules --- .../functional/api/test_notifications.py | 10 ++++ .../tests/functional/api/test_workflow_job.py | 54 +++++++++++++++++++ awx/main/tests/functional/conftest.py | 26 ++++++++- awx/main/tests/functional/test_copy.py | 18 +++++++ .../tests/functional/test_notifications.py | 29 ++++++++++ .../tests/functional/test_rbac_workflow.py | 24 --------- 6 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 awx/main/tests/functional/api/test_workflow_job.py diff --git a/awx/main/tests/functional/api/test_notifications.py b/awx/main/tests/functional/api/test_notifications.py index 92f6045191..431065396d 100644 --- a/awx/main/tests/functional/api/test_notifications.py +++ b/awx/main/tests/functional/api/test_notifications.py @@ -153,3 +153,13 @@ def test_post_org_approval_notification(get, post, admin, notification_template, response = get(url, admin) assert response.status_code == 200 assert len(response.data['results']) == 1 + + +@pytest.mark.django_db +def test_post_wfj_notification(get, post, admin, workflow_job, notification): + workflow_job.notifications.add(notification) + workflow_job.save() + url = reverse("api:workflow_job_notifications_list", kwargs={'pk': workflow_job.pk}) + response = get(url, admin) + assert response.status_code == 200 + assert len(response.data['results']) == 1 diff --git a/awx/main/tests/functional/api/test_workflow_job.py b/awx/main/tests/functional/api/test_workflow_job.py new file mode 100644 index 0000000000..36553258db --- /dev/null +++ b/awx/main/tests/functional/api/test_workflow_job.py @@ -0,0 +1,54 @@ +import pytest + + +from awx.api.versioning import reverse + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "is_admin, status", + [ + [True, 201], + [False, 403], + ], # if they're a WFJ admin, they get a 201 # if they're not a WFJ *nor* org admin, they get a 403 +) +def test_workflow_job_relaunch(workflow_job, post, admin_user, alice, is_admin, status): + url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk}) + if is_admin: + post(url, user=admin_user, expect=status) + else: + post(url, user=alice, expect=status) + + +@pytest.mark.django_db +def test_workflow_job_relaunch_failure(workflow_job, post, admin_user): + workflow_job.is_sliced_job = True + workflow_job.job_template = None + workflow_job.save() + url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk}) + post(url, user=admin_user, expect=400) + + +@pytest.mark.django_db +def test_workflow_job_relaunch_not_inventory_failure(workflow_job, post, admin_user): + workflow_job.is_sliced_job = True + workflow_job.inventory = None + workflow_job.save() + url = reverse("api:workflow_job_relaunch", kwargs={'pk': workflow_job.pk}) + post(url, user=admin_user, expect=400) + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "is_admin, status", + [ + [True, 202], + [False, 403], + ], # if they're a WFJ admin, they get a 202 # if they're not a WFJ *nor* org admin, they get a 403 +) +def test_workflow_job_cancel(workflow_job, post, admin_user, alice, is_admin, status): + url = reverse("api:workflow_job_cancel", kwargs={'pk': workflow_job.pk}) + if is_admin: + post(url, user=admin_user, expect=status) + else: + post(url, user=alice, expect=status) diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index e1284ce87c..3ce22d9f96 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -39,7 +39,7 @@ from awx.main.models.events import ( InventoryUpdateEvent, SystemJobEvent, ) -from awx.main.models.workflow import WorkflowJobTemplate +from awx.main.models.workflow import WorkflowJobTemplate, WorkflowJob from awx.main.models.ad_hoc_commands import AdHocCommand from awx.main.models.oauth import OAuth2Application as Application from awx.main.models.execution_environments import ExecutionEnvironment @@ -743,6 +743,30 @@ def system_job_factory(system_job_template, admin): return factory +@pytest.fixture +def wfjt(workflow_job_template_factory, organization): + objects = workflow_job_template_factory('test_workflow', organization=organization, persisted=True) + return objects.workflow_job_template + + +@pytest.fixture +def wfjt_with_nodes(workflow_job_template_factory, organization, job_template): + objects = workflow_job_template_factory( + 'test_workflow', organization=organization, workflow_job_template_nodes=[{'unified_job_template': job_template}], persisted=True + ) + return objects.workflow_job_template + + +@pytest.fixture +def wfjt_node(wfjt_with_nodes): + return wfjt_with_nodes.workflow_job_template_nodes.all()[0] + + +@pytest.fixture +def workflow_job(wfjt): + return wfjt.workflow_jobs.create(name='test_workflow') + + def dumps(value): return DjangoJSONEncoder().encode(value) diff --git a/awx/main/tests/functional/test_copy.py b/awx/main/tests/functional/test_copy.py index 0574f9ccbd..001ebfbc74 100644 --- a/awx/main/tests/functional/test_copy.py +++ b/awx/main/tests/functional/test_copy.py @@ -123,6 +123,24 @@ def test_inventory_copy(inventory, group_factory, post, get, alice, organization assert set(group_2_2_copy.hosts.all()) == set() +@pytest.mark.django_db +@pytest.mark.parametrize( + "is_admin, can_copy, status", + [ + [True, True, 200], + [False, False, 200], + ], +) +def test_workflow_job_template_copy_access(get, admin_user, alice, workflow_job_template, is_admin, can_copy, status): + url = reverse('api:workflow_job_template_copy', kwargs={'pk': workflow_job_template.pk}) + if is_admin: + response = get(url, user=admin_user, expect=status) + else: + workflow_job_template.organization.auditor_role.members.add(alice) + response = get(url, user=alice, expect=status) + assert response.data['can_copy'] == can_copy + + @pytest.mark.django_db def test_workflow_job_template_copy(workflow_job_template, post, get, admin, organization): ''' diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py index 08036db97c..4a5d2e8387 100644 --- a/awx/main/tests/functional/test_notifications.py +++ b/awx/main/tests/functional/test_notifications.py @@ -1,5 +1,6 @@ from unittest import mock import pytest +import json from requests.adapters import HTTPAdapter from requests.utils import select_proxy @@ -218,3 +219,31 @@ def test_webhook_notification_pointed_to_a_redirect_launch_endpoint(post, admin, ) assert n1.send("", n1.messages.get("success").get("body")) == 1 + + +@pytest.mark.django_db +def test_update_notification_template(admin, notification_template): + notification_template.messages['workflow_approval'] = { + "running": { + "message": None, + "body": None, + } + } + notification_template.save() + + workflow_approval_message = { + "approved": { + "message": None, + "body": None, + }, + "running": { + "message": "test-message", + "body": None, + }, + } + notification_template.messages['workflow_approval'] = workflow_approval_message + notification_template.save() + + subevents = sorted(notification_template.messages["workflow_approval"].keys()) + assert subevents == ["approved", "running"] + assert notification_template.messages['workflow_approval'] == workflow_approval_message diff --git a/awx/main/tests/functional/test_rbac_workflow.py b/awx/main/tests/functional/test_rbac_workflow.py index c2022783ac..0143399ba6 100644 --- a/awx/main/tests/functional/test_rbac_workflow.py +++ b/awx/main/tests/functional/test_rbac_workflow.py @@ -13,30 +13,6 @@ from rest_framework.exceptions import PermissionDenied from awx.main.models import InventorySource, JobLaunchConfig -@pytest.fixture -def wfjt(workflow_job_template_factory, organization): - objects = workflow_job_template_factory('test_workflow', organization=organization, persisted=True) - return objects.workflow_job_template - - -@pytest.fixture -def wfjt_with_nodes(workflow_job_template_factory, organization, job_template): - objects = workflow_job_template_factory( - 'test_workflow', organization=organization, workflow_job_template_nodes=[{'unified_job_template': job_template}], persisted=True - ) - return objects.workflow_job_template - - -@pytest.fixture -def wfjt_node(wfjt_with_nodes): - return wfjt_with_nodes.workflow_job_template_nodes.all()[0] - - -@pytest.fixture -def workflow_job(wfjt): - return wfjt.workflow_jobs.create(name='test_workflow') - - @pytest.mark.django_db class TestWorkflowJobTemplateAccess: def test_random_user_no_edit(self, wfjt, rando): From d32a5905e8edb4d1dd8def5bb9226b9ce9fcb060 Mon Sep 17 00:00:00 2001 From: Alexander Komarov Date: Fri, 30 Jul 2021 14:28:27 +0500 Subject: [PATCH 04/14] Remove unused imports --- awx/main/tests/functional/conftest.py | 2 +- awx/main/tests/functional/test_notifications.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 3ce22d9f96..c87f0a6c1a 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -39,7 +39,7 @@ from awx.main.models.events import ( InventoryUpdateEvent, SystemJobEvent, ) -from awx.main.models.workflow import WorkflowJobTemplate, WorkflowJob +from awx.main.models.workflow import WorkflowJobTemplate from awx.main.models.ad_hoc_commands import AdHocCommand from awx.main.models.oauth import OAuth2Application as Application from awx.main.models.execution_environments import ExecutionEnvironment diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py index 4a5d2e8387..092c970539 100644 --- a/awx/main/tests/functional/test_notifications.py +++ b/awx/main/tests/functional/test_notifications.py @@ -1,6 +1,5 @@ from unittest import mock import pytest -import json from requests.adapters import HTTPAdapter from requests.utils import select_proxy From 9b716235a2898b8a255779006f8b2faf5687251c Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Thu, 6 Apr 2023 00:52:37 -0400 Subject: [PATCH 05/14] update credential list examples in awx collection --- awx_collection/plugins/modules/bulk_job_launch.py | 3 +++ awx_collection/plugins/modules/job_launch.py | 4 +++- awx_collection/plugins/modules/job_template.py | 1 + awx_collection/plugins/modules/workflow_job_template.py | 4 +++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/bulk_job_launch.py b/awx_collection/plugins/modules/bulk_job_launch.py index 8aa5fca254..94b5415e33 100644 --- a/awx_collection/plugins/modules/bulk_job_launch.py +++ b/awx_collection/plugins/modules/bulk_job_launch.py @@ -186,6 +186,9 @@ EXAMPLES = ''' food: carrot color: orange limit: bar + credentials: + - "My Credential" + - "suplementary cred" extra_vars: # these override / extend extra_data at the job level food: grape animal: owl diff --git a/awx_collection/plugins/modules/job_launch.py b/awx_collection/plugins/modules/job_launch.py index 9a76f3a8b7..20f4fa7f73 100644 --- a/awx_collection/plugins/modules/job_launch.py +++ b/awx_collection/plugins/modules/job_launch.py @@ -151,7 +151,9 @@ EXAMPLES = ''' job_launch: job_template: "My Job Template" inventory: "My Inventory" - credential: "My Credential" + credentials: + - "My Credential" + - "suplementary cred" register: job - name: Wait for job max 120s job_wait: diff --git a/awx_collection/plugins/modules/job_template.py b/awx_collection/plugins/modules/job_template.py index 4508bc18d5..ef4b2d8aca 100644 --- a/awx_collection/plugins/modules/job_template.py +++ b/awx_collection/plugins/modules/job_template.py @@ -337,6 +337,7 @@ EXAMPLES = ''' playbook: "ping.yml" credentials: - "Local" + - "2nd credential" state: "present" controller_config_file: "~/tower_cli.cfg" survey_enabled: yes diff --git a/awx_collection/plugins/modules/workflow_job_template.py b/awx_collection/plugins/modules/workflow_job_template.py index 19954877b7..db9c646fda 100644 --- a/awx_collection/plugins/modules/workflow_job_template.py +++ b/awx_collection/plugins/modules/workflow_job_template.py @@ -461,7 +461,9 @@ EXAMPLES = ''' failure_nodes: - identifier: node201 always_nodes: [] - credentials: [] + credentials: + - local_cred + - suplementary cred - identifier: node201 unified_job_template: organization: From 0f4bac7aedce25f1cdf80acd2eaf85e679cd6863 Mon Sep 17 00:00:00 2001 From: Steffen Scheib Date: Mon, 20 Mar 2023 13:43:12 +0100 Subject: [PATCH 06/14] Add missing filtering mechanism for the Thycotic Devops Vault credential lookup --- awx/main/credential_plugins/dsv.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/awx/main/credential_plugins/dsv.py b/awx/main/credential_plugins/dsv.py index 9c89199710..78d5e6a4f0 100644 --- a/awx/main/credential_plugins/dsv.py +++ b/awx/main/credential_plugins/dsv.py @@ -35,8 +35,14 @@ dsv_inputs = { 'type': 'string', 'help_text': _('The secret path e.g. /test/secret1'), }, + { + 'id': 'secret_field', + 'label': _('Secret Field'), + 'help_text': _('The field to extract from the secret'), + 'type': 'string', + }, ], - 'required': ['tenant', 'client_id', 'client_secret', 'path'], + 'required': ['tenant', 'client_id', 'client_secret', 'path', 'secret_field'], } if settings.DEBUG: @@ -52,5 +58,5 @@ if settings.DEBUG: dsv_plugin = CredentialPlugin( 'Thycotic DevOps Secrets Vault', dsv_inputs, - lambda **kwargs: SecretsVault(**{k: v for (k, v) in kwargs.items() if k in [field['id'] for field in dsv_inputs['fields']]}).get_secret(kwargs['path']), + lambda **kwargs: SecretsVault(**{k: v for (k, v) in kwargs.items() if k in [field['id'] for field in dsv_inputs['fields']]}).get_secret(kwargs['path'])['data'][kwargs['secret_field']], ) From 2f68317e5fc32d331f11c04d6b84d1009a69c532 Mon Sep 17 00:00:00 2001 From: Steffen Scheib Date: Thu, 30 Mar 2023 17:03:55 +0200 Subject: [PATCH 07/14] Fixing api-lint error --- awx/main/credential_plugins/dsv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/credential_plugins/dsv.py b/awx/main/credential_plugins/dsv.py index 78d5e6a4f0..7ebbe60401 100644 --- a/awx/main/credential_plugins/dsv.py +++ b/awx/main/credential_plugins/dsv.py @@ -58,5 +58,5 @@ if settings.DEBUG: dsv_plugin = CredentialPlugin( 'Thycotic DevOps Secrets Vault', dsv_inputs, - lambda **kwargs: SecretsVault(**{k: v for (k, v) in kwargs.items() if k in [field['id'] for field in dsv_inputs['fields']]}).get_secret(kwargs['path'])['data'][kwargs['secret_field']], + lambda **kwargs: SecretsVault(**{k: v for (k, v) in kwargs.items() if k in [field['id'] for field in dsv_inputs['fields']]}).get_secret(kwargs['path'])['data'][kwargs['secret_field']], # fmt: skip ) From fba4e06c50f995b48c9a6f03934cc02078efa92b Mon Sep 17 00:00:00 2001 From: John Westcott IV <32551173+john-westcott-iv@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:02:52 -0400 Subject: [PATCH 08/14] Adding basic validation for local passwords (#13789) * Adding basic validation for local passwords * Adding edit screen * Fixing tests --- awx/api/serializers.py | 18 + .../tests/functional/api/test_serializers.py | 75 +++ awx/sso/conf.py | 44 ++ .../MiscAuthenticationEdit.js | 30 +- .../MiscAuthenticationEdit.test.js | 4 + .../shared/data.allSettingOptions.json | 515 +++++++++----- .../Setting/shared/data.allSettings.json | 630 ++++++++++-------- 7 files changed, 885 insertions(+), 431 deletions(-) create mode 100644 awx/main/tests/functional/api/test_serializers.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 4b3a62c841..13228331e0 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -995,6 +995,24 @@ class UserSerializer(BaseSerializer): django_validate_password(value) if not self.instance and value in (None, ''): raise serializers.ValidationError(_('Password required for new User.')) + + # Check if a password is too long + password_max_length = User._meta.get_field('password').max_length + if len(value) > password_max_length: + raise serializers.ValidationError(_('Password max length is {}'.format(password_max_length))) + if getattr(settings, 'LOCAL_PASSWORD_MIN_LENGTH', 0) and len(value) < getattr(settings, 'LOCAL_PASSWORD_MIN_LENGTH'): + raise serializers.ValidationError(_('Password must be at least {} characters long.'.format(getattr(settings, 'LOCAL_PASSWORD_MIN_LENGTH')))) + if getattr(settings, 'LOCAL_PASSWORD_MIN_DIGITS', 0) and sum(c.isdigit() for c in value) < getattr(settings, 'LOCAL_PASSWORD_MIN_DIGITS'): + raise serializers.ValidationError(_('Password must contain at least {} digits.'.format(getattr(settings, 'LOCAL_PASSWORD_MIN_DIGITS')))) + if getattr(settings, 'LOCAL_PASSWORD_MIN_UPPER', 0) and sum(c.isupper() for c in value) < getattr(settings, 'LOCAL_PASSWORD_MIN_UPPER'): + raise serializers.ValidationError( + _('Password must contain at least {} uppercase characters.'.format(getattr(settings, 'LOCAL_PASSWORD_MIN_UPPER'))) + ) + if getattr(settings, 'LOCAL_PASSWORD_MIN_SPECIAL', 0) and sum(not c.isalnum() for c in value) < getattr(settings, 'LOCAL_PASSWORD_MIN_SPECIAL'): + raise serializers.ValidationError( + _('Password must contain at least {} special characters.'.format(getattr(settings, 'LOCAL_PASSWORD_MIN_SPECIAL'))) + ) + return value def _update_password(self, obj, new_password): diff --git a/awx/main/tests/functional/api/test_serializers.py b/awx/main/tests/functional/api/test_serializers.py new file mode 100644 index 0000000000..ab31e186e9 --- /dev/null +++ b/awx/main/tests/functional/api/test_serializers.py @@ -0,0 +1,75 @@ +import pytest + +from django.test.utils import override_settings + +from rest_framework.serializers import ValidationError + +from awx.api.serializers import UserSerializer +from django.contrib.auth.models import User + + +@pytest.mark.parametrize( + "password,min_length,min_digits,min_upper,min_special,expect_error", + [ + # Test length + ("a", 1, 0, 0, 0, False), + ("a", 2, 0, 0, 0, True), + ("aa", 2, 0, 0, 0, False), + ("aaabcDEF123$%^", 2, 0, 0, 0, False), + # Test digits + ("a", 0, 1, 0, 0, True), + ("1", 0, 1, 0, 0, False), + ("1", 0, 2, 0, 0, True), + ("12", 0, 2, 0, 0, False), + ("12abcDEF123$%^", 0, 2, 0, 0, False), + # Test upper + ("a", 0, 0, 1, 0, True), + ("A", 0, 0, 1, 0, False), + ("A", 0, 0, 2, 0, True), + ("AB", 0, 0, 2, 0, False), + ("ABabcDEF123$%^", 0, 0, 2, 0, False), + # Test special + ("a", 0, 0, 0, 1, True), + ("!", 0, 0, 0, 1, False), + ("!", 0, 0, 0, 2, True), + ("!@", 0, 0, 0, 2, False), + ("!@abcDEF123$%^", 0, 0, 0, 2, False), + ], +) +@pytest.mark.django_db +def test_validate_password_rules(password, min_length, min_digits, min_upper, min_special, expect_error): + user_serializer = UserSerializer() + + # First test password with no params, this should always pass + try: + user_serializer.validate_password(password) + except ValidationError: + assert False, f"Password {password} should not have validation issue if no params are used" + + with override_settings( + LOCAL_PASSWORD_MIN_LENGTH=min_length, LOCAL_PASSWORD_MIN_DIGITS=min_digits, LOCAL_PASSWORD_MIN_UPPER=min_upper, LOCAL_PASSWORD_MIN_SPECIAL=min_special + ): + if expect_error: + with pytest.raises(ValidationError): + user_serializer.validate_password(password) + else: + try: + user_serializer.validate_password(password) + except ValidationError: + assert False, "validate_password raised an unexpected exception" + + +@pytest.mark.django_db +def test_validate_password_too_long(): + password_max_length = User._meta.get_field('password').max_length + password = "x" * password_max_length + + user_serializer = UserSerializer() + try: + user_serializer.validate_password(password) + except ValidationError: + assert False, f"Password {password} should not have validation" + + password = f"{password}x" + with pytest.raises(ValidationError): + user_serializer.validate_password(password) diff --git a/awx/sso/conf.py b/awx/sso/conf.py index ddfd80fd13..3cae57311c 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -1603,6 +1603,50 @@ register( ], ) +register( + 'LOCAL_PASSWORD_MIN_LENGTH', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of characters in local password'), + help_text=_('Minimum number of characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', +) + +register( + 'LOCAL_PASSWORD_MIN_DIGITS', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of digit characters in local password'), + help_text=_('Minimum number of digit characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', +) + +register( + 'LOCAL_PASSWORD_MIN_UPPER', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of uppercase characters in local password'), + help_text=_('Minimum number of uppercase characters required in a local password. 0 means no minimum'), + category=_('Authentication'), + category_slug='authentication', +) + +register( + 'LOCAL_PASSWORD_MIN_SPECIAL', + field_class=fields.IntegerField, + min_value=0, + default=0, + label=_('Minimum number of special characters in local password'), + help_text=_('Minimum number of special characters required in a local password. 0 means no minimum'), + 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'): diff --git a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js index a8b7814543..97240efdbc 100644 --- a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js +++ b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js @@ -54,7 +54,11 @@ function MiscAuthenticationEdit() { 'SOCIAL_AUTH_ORGANIZATION_MAP', 'SOCIAL_AUTH_TEAM_MAP', 'SOCIAL_AUTH_USER_FIELDS', - 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL' + 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL', + 'LOCAL_PASSWORD_MIN_LENGTH', + 'LOCAL_PASSWORD_MIN_DIGITS', + 'LOCAL_PASSWORD_MIN_UPPER', + 'LOCAL_PASSWORD_MIN_SPECIAL' ); const authenticationData = { @@ -247,6 +251,30 @@ function MiscAuthenticationEdit() { name="SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL" config={authentication.SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL} /> + + + + {submitError && } {revertError && } diff --git a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.test.js b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.test.js index d84b759113..3e790a7544 100644 --- a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.test.js +++ b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.test.js @@ -33,6 +33,10 @@ const authenticationData = { SOCIAL_AUTH_TEAM_MAP: null, SOCIAL_AUTH_USER_FIELDS: null, SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL: false, + LOCAL_PASSWORD_MIN_LENGTH: 0, + LOCAL_PASSWORD_MIN_DIGITS: 0, + LOCAL_PASSWORD_MIN_UPPER: 0, + LOCAL_PASSWORD_MIN_SPECIAL: 0, }; describe('', () => { diff --git a/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json b/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json index c68b11474e..fa397c07f5 100644 --- a/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json +++ b/awx/ui/src/screens/Setting/shared/data.allSettingOptions.json @@ -204,7 +204,7 @@ "type": "list", "required": false, "label": "Paths to expose to isolated jobs", - "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.", + "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line. Volumes will be mounted from the execution node to the container. The supported format is HOST-DIR[:CONTAINER-DIR[:OPTIONS]]. ", "category": "Jobs", "category_slug": "jobs", "default": [], @@ -231,26 +231,36 @@ "read_only": false } }, + "AWX_RUNNER_KEEPALIVE_SECONDS": { + "type": "integer", + "required": true, + "label": "K8S Ansible Runner Keep-Alive Message Interval", + "help_text": "Only applies to jobs running in a Container Group. If not 0, send a message every so-many seconds to keep connection open.", + "category": "Jobs", + "category_slug": "jobs", + "placeholder": 240, + "default": 0 + }, "GALAXY_TASK_ENV": { "type": "nested object", - "required": true, + "required": true, "label": "Environment Variables for Galaxy Commands", - "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.", - "category": "Jobs", - "category_slug": "jobs", - "placeholder": { - "HTTP_PROXY": "myproxy.local:8080" - }, - "default": { - "ANSIBLE_FORCE_COLOR": "false", - "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no" + "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.", + "category": "Jobs", + "category_slug": "jobs", + "placeholder": { + "HTTP_PROXY": "myproxy.local:8080" }, - "child": { - "type": "string", - "required": true, - "read_only": false + "default": { + "ANSIBLE_FORCE_COLOR": "false", + "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no" + }, + "child": { + "type": "string", + "required": true, + "read_only": false } - }, + }, "INSIGHTS_TRACKING_STATE": { "type": "boolean", "required": false, @@ -334,6 +344,16 @@ "category_slug": "jobs", "default": 1024 }, + "MAX_WEBSOCKET_EVENT_RATE": { + "type": "integer", + "required": false, + "label": "Job Event Maximum Websocket Messages Per Second", + "help_text": "Maximum number of messages to update the UI live job output with per second. Value of 0 means no limit.", + "min_value": 0, + "category": "Jobs", + "category_slug": "jobs", + "default": 30 + }, "SCHEDULE_MAX_JOBS": { "type": "integer", "required": true, @@ -344,16 +364,6 @@ "category_slug": "jobs", "default": 10 }, - "AWX_RUNNER_KEEPALIVE_SECONDS": { - "type": "integer", - "required": true, - "label": "K8S Ansible Runner Keep-Alive Message Interval", - "help_text": "Only applies to K8S deployments and container_group jobs. If not 0, send a message every so-many seconds to keep connection open.", - "category": "Jobs", - "category_slug": "jobs", - "placeholder": 240, - "default": 0 - }, "AWX_ANSIBLE_CALLBACK_PLUGINS": { "type": "list", "required": false, @@ -383,7 +393,7 @@ "type": "integer", "required": false, "label": "Default Job Idle Timeout", - "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to used default idle_timeout is 600s.", + "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to indicate that no idle timeout should be imposed.", "min_value": 0, "category": "Jobs", "category_slug": "jobs", @@ -489,10 +499,16 @@ "type": "list", "required": false, "label": "Loggers Sending Data to Log Aggregator Form", - "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs.", + "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs\nbroadcast_websocket - errors pertaining to websockets broadcast metrics\n", "category": "Logging", "category_slug": "logging", - "default": ["awx", "activity_stream", "job_events", "system_tracking"], + "default": [ + "awx", + "activity_stream", + "job_events", + "system_tracking", + "broadcast_websocket" + ], "child": { "type": "string", "required": true, @@ -639,15 +655,51 @@ "unit": "seconds", "default": 14400 }, + "BULK_JOB_MAX_LAUNCH": { + "type": "integer", + "required": false, + "label": "Max jobs to allow bulk jobs to launch", + "help_text": "Max jobs to allow bulk jobs to launch", + "category": "Bulk Actions", + "category_slug": "bulk", + "default": 100 + }, + "BULK_HOST_MAX_CREATE": { + "type": "integer", + "required": false, + "label": "Max number of hosts to allow to be created in a single bulk action", + "help_text": "Max number of hosts to allow to be created in a single bulk action", + "category": "Bulk Actions", + "category_slug": "bulk", + "default": 100 + }, "UI_NEXT": { "type": "boolean", "required": false, "label": "Enable Preview of New User Interface", - "help_text": "'Enable preview of new user interface.", + "help_text": "Enable preview of new user interface.", "category": "System", "category_slug": "system", "default": true }, + "SUBSCRIPTION_USAGE_MODEL": { + "type": "choice", + "required": false, + "label": "Defines subscription usage model and shows Host Metrics", + "category": "System", + "category_slug": "system", + "default": "", + "choices": [ + [ + "", + "Default model for AWX - no subscription. Deletion of host_metrics will not be considered for purposes of managed host counting" + ], + [ + "unique_managed_hosts", + "Usage based on unique managed nodes in a large historical time frame and delete functionality for no longer used managed nodes" + ] + ] + }, "SESSION_COOKIE_AGE": { "type": "integer", "required": true, @@ -740,6 +792,15 @@ ["detailed", "Detailed"] ] }, + "ALLOW_METRICS_FOR_ANONYMOUS_USERS": { + "type": "boolean", + "required": false, + "label": "Allow anonymous users to poll metrics", + "help_text": "If true, anonymous users are allowed to poll metrics.", + "category": "Authentication", + "category_slug": "authentication", + "default": false + }, "CUSTOM_LOGIN_INFO": { "type": "string", "required": false, @@ -782,7 +843,7 @@ "type": "nested object", "required": false, "label": "Social Auth Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Authentication", "category_slug": "authentication", "placeholder": { @@ -868,39 +929,6 @@ "category_slug": "authentication", "default": false }, - "SOCIAL_AUTH_OIDC_KEY": { - "type": "string", - "label": "OIDC Key", - "help_text": "The OIDC key (Client ID) from your IDP.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_SECRET": { - "type": "string", - "label": "OIDC Secret", - "help_text": "The OIDC secret (Client Secret) from your IDP.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": { - "type": "string", - "label": "OIDC Provider URL", - "help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_VERIFY_SSL": { - "type": "boolean", - "required": false, - "label": "Verify OIDC Provider Certificate", - "help_text": "Verify the OIDC provider ssl certificate.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": true - }, "AUTH_LDAP_SERVER_URI": { "type": "string", "required": false, @@ -2726,7 +2754,7 @@ "type": "nested object", "required": false, "label": "Google OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Google OAuth2", "category_slug": "google-oauth2", "placeholder": { @@ -2810,7 +2838,7 @@ "type": "nested object", "required": false, "label": "GitHub OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub OAuth2", "category_slug": "github", "placeholder": { @@ -2903,7 +2931,7 @@ "type": "nested object", "required": false, "label": "GitHub Organization OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Organization OAuth2", "category_slug": "github-org", "placeholder": { @@ -2996,7 +3024,7 @@ "type": "nested object", "required": false, "label": "GitHub Team OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Team OAuth2", "category_slug": "github-team", "placeholder": { @@ -3098,7 +3126,7 @@ "type": "nested object", "required": false, "label": "GitHub Enterprise OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise OAuth2", "category_slug": "github-enterprise", "placeholder": { @@ -3209,7 +3237,7 @@ "type": "nested object", "required": false, "label": "GitHub Enterprise Organization OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise Organization OAuth2", "category_slug": "github-enterprise-org", "placeholder": { @@ -3320,7 +3348,7 @@ "type": "nested object", "required": false, "label": "GitHub Enterprise Team OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise Team OAuth2", "category_slug": "github-enterprise-team", "placeholder": { @@ -3404,7 +3432,7 @@ "type": "nested object", "required": false, "label": "Azure AD OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Azure AD OAuth2", "category_slug": "azuread-oauth2", "placeholder": { @@ -3466,6 +3494,42 @@ } } }, + "SOCIAL_AUTH_OIDC_KEY": { + "type": "string", + "required": false, + "label": "OIDC Key", + "help_text": "The OIDC key (Client ID) from your IDP.", + "category": "Generic OIDC", + "category_slug": "oidc", + "default": null + }, + "SOCIAL_AUTH_OIDC_SECRET": { + "type": "string", + "required": false, + "label": "OIDC Secret", + "help_text": "The OIDC secret (Client Secret) from your IDP.", + "category": "Generic OIDC", + "category_slug": "oidc", + "default": "" + }, + "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": { + "type": "string", + "required": false, + "label": "OIDC Provider URL", + "help_text": "The URL for your OIDC provider including the path up to /.well-known/openid-configuration", + "category": "Generic OIDC", + "category_slug": "oidc", + "default": "" + }, + "SOCIAL_AUTH_OIDC_VERIFY_SSL": { + "type": "boolean", + "required": false, + "label": "Verify OIDC Provider Certificate", + "help_text": "Verify the OIDC provider ssl certificate.", + "category": "Generic OIDC", + "category_slug": "oidc", + "default": true + }, "SAML_AUTO_CREATE_OBJECTS": { "type": "boolean", "required": false, @@ -3678,7 +3742,7 @@ "type": "nested object", "required": false, "label": "SAML Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "SAML", "category_slug": "saml", "placeholder": { @@ -3813,20 +3877,62 @@ "help_text": "Used to map super users and system auditors from SAML.", "category": "SAML", "category_slug": "saml", - "placeholder": { - "is_superuser_attr": "saml_attr", - "is_superuser_value": "value", - "is_superuser_role": "saml_role", - "is_system_auditor_attr": "saml_attr", - "is_system_auditor_value": "value", - "is_system_auditor_role": "saml_role" - }, + "placeholder": [ + ["is_superuser_attr", "saml_attr"], + ["is_superuser_value", ["value"]], + ["is_superuser_role", ["saml_role"]], + ["remove_superusers", true], + ["is_system_auditor_attr", "saml_attr"], + ["is_system_auditor_value", ["value"]], + ["is_system_auditor_role", ["saml_role"]], + ["remove_system_auditors", true] + ], "default": {}, "child": { "type": "field", "required": true, "read_only": false } + }, + "LOCAL_PASSWORD_MIN_LENGTH": { + "type": "integer", + "required": false, + "label": "Minimum number of characters in local password", + "help_text": "Minimum number of characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "default": 0 + }, + "LOCAL_PASSWORD_MIN_DIGITS": { + "type": "integer", + "required": false, + "label": "Minimum number of digit characters in local password", + "help_text": "Minimum number of digit characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "default": 0 + }, + "LOCAL_PASSWORD_MIN_UPPER": { + "type": "integer", + "required": false, + "label": "Minimum number of uppercase characters in local password", + "help_text": "Minimum number of uppercase characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "default": 0 + }, + "LOCAL_PASSWORD_MIN_SPECIAL": { + "type": "integer", + "required": false, + "label": "Minimum number of special characters in local password", + "help_text": "Minimum number of special characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "default": 0 } }, "GET": { @@ -3873,7 +3979,7 @@ "REMOTE_HOST_HEADERS": { "type": "list", "label": "Remote Host Headers", - "help_text": "HTTP headers and meta keys to search to determine remote host name or IP. Add additional items to this list, such as \"HTTP_X_FORWARDED_FOR\", if behind a reverse proxy. See the \"Proxy Support\" section of the Adminstrator guide for more details.", + "help_text": "HTTP headers and meta keys to search to determine remote host name or IP. Add additional items to this list, such as \"HTTP_X_FORWARDED_FOR\", if behind a reverse proxy. See the \"Proxy Support\" section of the AAP Installation guide for more details.", "category": "System", "category_slug": "system", "defined_in_file": false, @@ -3950,6 +4056,20 @@ "category_slug": "system", "defined_in_file": false }, + "DEFAULT_CONTROL_PLANE_QUEUE_NAME": { + "type": "string", + "label": "The instance group where control plane tasks run", + "category": "System", + "category_slug": "system", + "defined_in_file": false + }, + "DEFAULT_EXECUTION_QUEUE_NAME": { + "type": "string", + "label": "The instance group where user jobs run (currently only on non-VM installs)", + "category": "System", + "category_slug": "system", + "defined_in_file": false + }, "DEFAULT_EXECUTION_ENVIRONMENT": { "type": "field", "label": "Global default execution environment", @@ -4004,7 +4124,7 @@ "AWX_ISOLATION_SHOW_PATHS": { "type": "list", "label": "Paths to expose to isolated jobs", - "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.", + "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line. Volumes will be mounted from the execution node to the container. The supported format is HOST-DIR[:CONTAINER-DIR[:OPTIONS]]. ", "category": "Jobs", "category_slug": "jobs", "defined_in_file": false, @@ -4023,26 +4143,25 @@ "type": "string" } }, + "AWX_RUNNER_KEEPALIVE_SECONDS": { + "type": "integer", + "label": "K8S Ansible Runner Keep-Alive Message Interval", + "help_text": "Only applies to jobs running in a Container Group. If not 0, send a message every so-many seconds to keep connection open.", + "category": "Jobs", + "category_slug": "jobs", + "defined_in_file": false + }, "GALAXY_TASK_ENV": { "type": "nested object", - "required": true, "label": "Environment Variables for Galaxy Commands", - "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.", - "category": "Jobs", - "category_slug": "jobs", - "placeholder": { - "HTTP_PROXY": "myproxy.local:8080" - }, - "default": { - "ANSIBLE_FORCE_COLOR": "false", - "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no" - }, - "child": { - "type": "string", - "required": true, - "read_only": false + "help_text": "Additional environment variables set for invocations of ansible-galaxy within project updates. Useful if you must use a proxy server for ansible-galaxy but not git.", + "category": "Jobs", + "category_slug": "jobs", + "defined_in_file": false, + "child": { + "type": "string" } - }, + }, "INSIGHTS_TRACKING_STATE": { "type": "boolean", "label": "Gather data for Automation Analytics", @@ -4117,6 +4236,15 @@ "category_slug": "jobs", "defined_in_file": false }, + "MAX_WEBSOCKET_EVENT_RATE": { + "type": "integer", + "label": "Job Event Maximum Websocket Messages Per Second", + "help_text": "Maximum number of messages to update the UI live job output with per second. Value of 0 means no limit.", + "min_value": 0, + "category": "Jobs", + "category_slug": "jobs", + "defined_in_file": false + }, "SCHEDULE_MAX_JOBS": { "type": "integer", "label": "Maximum Scheduled Jobs", @@ -4126,15 +4254,6 @@ "category_slug": "jobs", "defined_in_file": false }, - "AWX_RUNNER_KEEPALIVE_SECONDS": { - "type": "integer", - "label": "K8S Ansible Runner Keep-Alive Message Interval", - "help_text": "Only applies to K8S deployments and container_group jobs. If not 0, send a message every so-many seconds to keep connection open.", - "category": "Jobs", - "category_slug": "jobs", - "placeholder": 240, - "default": 0 - }, "AWX_ANSIBLE_CALLBACK_PLUGINS": { "type": "list", "label": "Ansible Callback Plugins", @@ -4159,7 +4278,7 @@ "DEFAULT_JOB_IDLE_TIMEOUT": { "type": "integer", "label": "Default Job Idle Timeout", - "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to used default idle_timeout is 600s.", + "help_text": "If no output is detected from ansible in this number of seconds the execution will be terminated. Use value of 0 to indicate that no idle timeout should be imposed.", "min_value": 0, "category": "Jobs", "category_slug": "jobs", @@ -4255,7 +4374,7 @@ "LOG_AGGREGATOR_LOGGERS": { "type": "list", "label": "Loggers Sending Data to Log Aggregator Form", - "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs.", + "help_text": "List of loggers that will send HTTP logs to the collector, these can include any or all of: \nawx - service logs\nactivity_stream - activity stream records\njob_events - callback data from Ansible job events\nsystem_tracking - facts gathered from scan jobs\nbroadcast_websocket - errors pertaining to websockets broadcast metrics\n", "category": "Logging", "category_slug": "logging", "defined_in_file": false, @@ -4359,12 +4478,11 @@ }, "API_400_ERROR_LOG_FORMAT": { "type": "string", - "required": false, "label": "Log Format For API 4XX Errors", "help_text": "The format of logged messages when an API 4XX error occurs, the following variables will be substituted: \nstatus_code - The HTTP status code of the error\nuser_name - The user name attempting to use the API\nurl_path - The URL path to the API endpoint called\nremote_addr - The remote address seen for the user\nerror - The error set by the api endpoint\nVariables need to be in the format {}.", "category": "Logging", "category_slug": "logging", - "default": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}" + "defined_in_file": false }, "AUTOMATION_ANALYTICS_LAST_GATHER": { "type": "datetime", @@ -4390,6 +4508,30 @@ "defined_in_file": false, "unit": "seconds" }, + "IS_K8S": { + "type": "boolean", + "label": "Is k8s", + "help_text": "Indicates whether the instance is part of a kubernetes-based deployment.", + "category": "System", + "category_slug": "system", + "defined_in_file": false + }, + "BULK_JOB_MAX_LAUNCH": { + "type": "integer", + "label": "Max jobs to allow bulk jobs to launch", + "help_text": "Max jobs to allow bulk jobs to launch", + "category": "Bulk Actions", + "category_slug": "bulk", + "defined_in_file": false + }, + "BULK_HOST_MAX_CREATE": { + "type": "integer", + "label": "Max number of hosts to allow to be created in a single bulk action", + "help_text": "Max number of hosts to allow to be created in a single bulk action", + "category": "Bulk Actions", + "category_slug": "bulk", + "defined_in_file": false + }, "UI_NEXT": { "type": "boolean", "label": "Enable Preview of New User Interface", @@ -4398,6 +4540,23 @@ "category_slug": "system", "defined_in_file": false }, + "SUBSCRIPTION_USAGE_MODEL": { + "type": "choice", + "label": "Defines subscription usage model and shows Host Metrics", + "category": "System", + "category_slug": "system", + "defined_in_file": false, + "choices": [ + [ + "", + "Default model for AWX - no subscription. Deletion of host_metrics will not be considered for purposes of managed host counting" + ], + [ + "unique_managed_hosts", + "Usage based on unique managed nodes in a large historical time frame and delete functionality for no longer used managed nodes" + ] + ] + }, "SESSION_COOKIE_AGE": { "type": "integer", "label": "Idle Time Force Log Out", @@ -4463,6 +4622,14 @@ "category_slug": "authentication", "defined_in_file": false }, + "ALLOW_METRICS_FOR_ANONYMOUS_USERS": { + "type": "boolean", + "label": "Allow anonymous users to poll metrics", + "help_text": "If true, anonymous users are allowed to poll metrics.", + "category": "Authentication", + "category_slug": "authentication", + "defined_in_file": false + }, "PENDO_TRACKING_STATE": { "type": "choice", "label": "User Analytics Tracking State", @@ -4523,7 +4690,7 @@ "SOCIAL_AUTH_ORGANIZATION_MAP": { "type": "nested object", "label": "Social Auth Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Authentication", "category_slug": "authentication", "defined_in_file": false, @@ -4569,39 +4736,7 @@ "help_text": "Enabling this setting will tell social auth to use the full Email as username instead of the full name", "category": "Authentication", "category_slug": "authentication", - "default": false - }, - "SOCIAL_AUTH_OIDC_KEY": { - "type": "string", - "label": "OIDC Key", - "help_text": "The OIDC key (Client ID) from your IDP.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_SECRET": { - "type": "string", - "label": "OIDC Secret", - "help_text": "The OIDC secret (Client Secret) from your IDP.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": { - "type": "string", - "label": "OIDC Provider URL", - "help_text": "The URL for your OIDC provider, e.g.: http(s)://hostname/.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": "" - }, - "SOCIAL_AUTH_OIDC_VERIFY_SSL": { - "type": "boolean", - "label": "Verify OIDC Provider Certificate", - "help_text": "Verify the OIDC provider ssl certificate.", - "category": "Generic OIDC", - "category_slug": "oidc", - "default": true + "defined_in_file": false }, "AUTH_LDAP_SERVER_URI": { "type": "string", @@ -5830,7 +5965,7 @@ "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP": { "type": "nested object", "label": "Google OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Google OAuth2", "category_slug": "google-oauth2", "defined_in_file": false, @@ -5886,7 +6021,7 @@ "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub OAuth2", "category_slug": "github", "defined_in_file": false, @@ -5950,7 +6085,7 @@ "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub Organization OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Organization OAuth2", "category_slug": "github-org", "defined_in_file": false, @@ -6014,7 +6149,7 @@ "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub Team OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Team OAuth2", "category_slug": "github-team", "defined_in_file": false, @@ -6086,7 +6221,7 @@ "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub Enterprise OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise OAuth2", "category_slug": "github-enterprise", "defined_in_file": false, @@ -6166,7 +6301,7 @@ "SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub Enterprise Organization OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise Organization OAuth2", "category_slug": "github-enterprise-org", "defined_in_file": false, @@ -6246,7 +6381,7 @@ "SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": { "type": "nested object", "label": "GitHub Enterprise Team OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "GitHub Enterprise Team OAuth2", "category_slug": "github-enterprise-team", "defined_in_file": false, @@ -6302,7 +6437,7 @@ "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP": { "type": "nested object", "label": "Azure AD OAuth2 Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "Azure AD OAuth2", "category_slug": "azuread-oauth2", "defined_in_file": false, @@ -6331,6 +6466,38 @@ } } }, + "SOCIAL_AUTH_OIDC_KEY": { + "type": "string", + "label": "OIDC Key", + "help_text": "The OIDC key (Client ID) from your IDP.", + "category": "Generic OIDC", + "category_slug": "oidc", + "defined_in_file": false + }, + "SOCIAL_AUTH_OIDC_SECRET": { + "type": "string", + "label": "OIDC Secret", + "help_text": "The OIDC secret (Client Secret) from your IDP.", + "category": "Generic OIDC", + "category_slug": "oidc", + "defined_in_file": false + }, + "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": { + "type": "string", + "label": "OIDC Provider URL", + "help_text": "The URL for your OIDC provider including the path up to /.well-known/openid-configuration", + "category": "Generic OIDC", + "category_slug": "oidc", + "defined_in_file": false + }, + "SOCIAL_AUTH_OIDC_VERIFY_SSL": { + "type": "boolean", + "label": "Verify OIDC Provider Certificate", + "help_text": "Verify the OIDC provider ssl certificate.", + "category": "Generic OIDC", + "category_slug": "oidc", + "defined_in_file": false + }, "SAML_AUTO_CREATE_OBJECTS": { "type": "boolean", "label": "Automatically Create Organizations and Teams on SAML Login", @@ -6469,7 +6636,7 @@ "SOCIAL_AUTH_SAML_ORGANIZATION_MAP": { "type": "nested object", "label": "SAML Organization Map", - "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the \ndocumentation.", + "help_text": "Mapping to organization admins/users from social auth accounts. This setting\ncontrols which users are placed into which organizations based on their\nusername and email address. Configuration details are available in the\ndocumentation.", "category": "SAML", "category_slug": "saml", "defined_in_file": false, @@ -6522,7 +6689,7 @@ }, "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": { "type": "nested object", - "label": "SAML User Flags Attribute Mapping", + "label": "SAML User Flags Attribute Mapping", "help_text": "Used to map super users and system auditors from SAML.", "category": "SAML", "category_slug": "saml", @@ -6531,6 +6698,42 @@ "type": "field" } }, + "LOCAL_PASSWORD_MIN_LENGTH": { + "type": "integer", + "label": "Minimum number of characters in local password", + "help_text": "Minimum number of characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "defined_in_file": false + }, + "LOCAL_PASSWORD_MIN_DIGITS": { + "type": "integer", + "label": "Minimum number of digit characters in local password", + "help_text": "Minimum number of digit characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "defined_in_file": false + }, + "LOCAL_PASSWORD_MIN_UPPER": { + "type": "integer", + "label": "Minimum number of uppercase characters in local password", + "help_text": "Minimum number of uppercase characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "defined_in_file": false + }, + "LOCAL_PASSWORD_MIN_SPECIAL": { + "type": "integer", + "label": "Minimum number of special characters in local password", + "help_text": "Minimum number of special characters required in a local password. 0 means no minimum", + "min_value": 0, + "category": "Authentication", + "category_slug": "authentication", + "defined_in_file": false + }, "NAMED_URL_FORMATS": { "type": "nested object", "label": "Formats of all available named urls", diff --git a/awx/ui/src/screens/Setting/shared/data.allSettings.json b/awx/ui/src/screens/Setting/shared/data.allSettings.json index bf73ce0308..a23c1cfba3 100644 --- a/awx/ui/src/screens/Setting/shared/data.allSettings.json +++ b/awx/ui/src/screens/Setting/shared/data.allSettings.json @@ -1,19 +1,19 @@ { - "ACTIVITY_STREAM_ENABLED":true, - "ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC":false, - "ORG_ADMINS_CAN_SEE_ALL_USERS":true, - "MANAGE_ORGANIZATION_AUTH":true, - "DISABLE_LOCAL_AUTH":false, - "TOWER_URL_BASE":"https://localhost:3000", - "REMOTE_HOST_HEADERS":["REMOTE_ADDR","REMOTE_HOST"], - "PROXY_IP_ALLOWED_LIST":[], - "LICENSE":{}, - "REDHAT_USERNAME":"", - "REDHAT_PASSWORD":"", - "AUTOMATION_ANALYTICS_URL":"https://example.com", - "INSTALL_UUID":"3f5a4d68-3a94-474c-a3c0-f23a33122ce6", - "CUSTOM_VENV_PATHS":[], - "AD_HOC_COMMANDS":[ + "ACTIVITY_STREAM_ENABLED": true, + "ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC": false, + "ORG_ADMINS_CAN_SEE_ALL_USERS": true, + "MANAGE_ORGANIZATION_AUTH": true, + "DISABLE_LOCAL_AUTH": false, + "TOWER_URL_BASE": "https://localhost:3000", + "REMOTE_HOST_HEADERS": ["REMOTE_ADDR", "REMOTE_HOST"], + "PROXY_IP_ALLOWED_LIST": [], + "LICENSE": {}, + "REDHAT_USERNAME": "", + "REDHAT_PASSWORD": "", + "AUTOMATION_ANALYTICS_URL": "https://example.com", + "INSTALL_UUID": "3f5a4d68-3a94-474c-a3c0-f23a33122ce6", + "CUSTOM_VENV_PATHS": [], + "AD_HOC_COMMANDS": [ "command", "shell", "yum", @@ -34,278 +34,360 @@ "win_group", "win_user" ], - "ALLOW_JINJA_IN_EXTRA_VARS":"template", - "AWX_ISOLATION_BASE_PATH":"/tmp", - "AWX_ISOLATION_SHOW_PATHS":[], - "AWX_TASK_ENV":{}, + "ALLOW_JINJA_IN_EXTRA_VARS": "template", + "AWX_ISOLATION_BASE_PATH": "/tmp", + "AWX_ISOLATION_SHOW_PATHS": [], + "AWX_TASK_ENV": {}, "GALAXY_TASK_ENV": { "ANSIBLE_FORCE_COLOR": "false", "GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no" - }, - "INSIGHTS_TRACKING_STATE":false, - "PROJECT_UPDATE_VVV":false, - "AWX_ROLES_ENABLED":true, - "AWX_COLLECTIONS_ENABLED":true, - "AWX_SHOW_PLAYBOOK_LINKS":false, - "GALAXY_IGNORE_CERTS":false, - "STDOUT_MAX_BYTES_DISPLAY":1048576, - "EVENT_STDOUT_MAX_BYTES_DISPLAY":1024, - "SCHEDULE_MAX_JOBS":10, - "AWX_RUNNER_KEEPALIVE_SECONDS": 0, - "AWX_ANSIBLE_CALLBACK_PLUGINS":[], - "DEFAULT_JOB_TIMEOUT":0, - "DEFAULT_JOB_IDLE_TIMEOUT":0, - "DEFAULT_INVENTORY_UPDATE_TIMEOUT":0, - "DEFAULT_PROJECT_UPDATE_TIMEOUT":0, - "ANSIBLE_FACT_CACHE_TIMEOUT":0, - "MAX_FORKS":200, - "LOG_AGGREGATOR_HOST":null, - "LOG_AGGREGATOR_PORT":null, - "LOG_AGGREGATOR_TYPE":null, - "LOG_AGGREGATOR_USERNAME":"", - "LOG_AGGREGATOR_PASSWORD":"", - "LOG_AGGREGATOR_LOGGERS":["awx","activity_stream","job_events","system_tracking"], - "LOG_AGGREGATOR_INDIVIDUAL_FACTS":false, - "LOG_AGGREGATOR_ENABLED":true, - "LOG_AGGREGATOR_TOWER_UUID":"", - "LOG_AGGREGATOR_PROTOCOL":"https", - "LOG_AGGREGATOR_TCP_TIMEOUT":5, - "LOG_AGGREGATOR_VERIFY_CERT":true, - "LOG_AGGREGATOR_LEVEL":"INFO", - "LOG_AGGREGATOR_MAX_DISK_USAGE_GB":1, - "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH":"/var/lib/awx", - "LOG_AGGREGATOR_RSYSLOGD_DEBUG":false, - "API_400_ERROR_LOG_FORMAT":"status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}", - "AUTOMATION_ANALYTICS_LAST_GATHER":null, - "AUTOMATION_ANALYTICS_GATHER_INTERVAL":14400, - "SESSION_COOKIE_AGE":1800, - "SESSIONS_PER_USER":-1, - "AUTH_BASIC_ENABLED":true, - "OAUTH2_PROVIDER":{ - "ACCESS_TOKEN_EXPIRE_SECONDS":31536000000, - "REFRESH_TOKEN_EXPIRE_SECONDS":2628000, - "AUTHORIZATION_CODE_EXPIRE_SECONDS":600 }, - "ALLOW_OAUTH2_FOR_EXTERNAL_USERS":false, - "LOGIN_REDIRECT_OVERRIDE":"", - "PENDO_TRACKING_STATE":"off", - "CUSTOM_LOGIN_INFO":"", - "CUSTOM_LOGO":"", - "MAX_UI_JOB_EVENTS":4000, - "UI_LIVE_UPDATES_ENABLED":true, - "AUTHENTICATION_BACKENDS":[ + "INSIGHTS_TRACKING_STATE": false, + "PROJECT_UPDATE_VVV": false, + "AWX_ROLES_ENABLED": true, + "AWX_COLLECTIONS_ENABLED": true, + "AWX_SHOW_PLAYBOOK_LINKS": false, + "GALAXY_IGNORE_CERTS": false, + "STDOUT_MAX_BYTES_DISPLAY": 1048576, + "EVENT_STDOUT_MAX_BYTES_DISPLAY": 1024, + "SCHEDULE_MAX_JOBS": 10, + "AWX_RUNNER_KEEPALIVE_SECONDS": 0, + "AWX_ANSIBLE_CALLBACK_PLUGINS": [], + "DEFAULT_JOB_TIMEOUT": 0, + "DEFAULT_JOB_IDLE_TIMEOUT": 0, + "DEFAULT_INVENTORY_UPDATE_TIMEOUT": 0, + "DEFAULT_PROJECT_UPDATE_TIMEOUT": 0, + "ANSIBLE_FACT_CACHE_TIMEOUT": 0, + "MAX_FORKS": 200, + "LOG_AGGREGATOR_HOST": null, + "LOG_AGGREGATOR_PORT": null, + "LOG_AGGREGATOR_TYPE": null, + "LOG_AGGREGATOR_USERNAME": "", + "LOG_AGGREGATOR_PASSWORD": "", + "LOG_AGGREGATOR_LOGGERS": [ + "awx", + "activity_stream", + "job_events", + "system_tracking" + ], + "LOG_AGGREGATOR_INDIVIDUAL_FACTS": false, + "LOG_AGGREGATOR_ENABLED": true, + "LOG_AGGREGATOR_TOWER_UUID": "", + "LOG_AGGREGATOR_PROTOCOL": "https", + "LOG_AGGREGATOR_TCP_TIMEOUT": 5, + "LOG_AGGREGATOR_VERIFY_CERT": true, + "LOG_AGGREGATOR_LEVEL": "INFO", + "LOG_AGGREGATOR_MAX_DISK_USAGE_GB": 1, + "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH": "/var/lib/awx", + "LOG_AGGREGATOR_RSYSLOGD_DEBUG": false, + "API_400_ERROR_LOG_FORMAT": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}", + "AUTOMATION_ANALYTICS_LAST_GATHER": null, + "AUTOMATION_ANALYTICS_GATHER_INTERVAL": 14400, + "SESSION_COOKIE_AGE": 1800, + "SESSIONS_PER_USER": -1, + "AUTH_BASIC_ENABLED": true, + "OAUTH2_PROVIDER": { + "ACCESS_TOKEN_EXPIRE_SECONDS": 31536000000, + "REFRESH_TOKEN_EXPIRE_SECONDS": 2628000, + "AUTHORIZATION_CODE_EXPIRE_SECONDS": 600 + }, + "ALLOW_OAUTH2_FOR_EXTERNAL_USERS": false, + "LOGIN_REDIRECT_OVERRIDE": "", + "PENDO_TRACKING_STATE": "off", + "CUSTOM_LOGIN_INFO": "", + "CUSTOM_LOGO": "", + "MAX_UI_JOB_EVENTS": 4000, + "UI_LIVE_UPDATES_ENABLED": true, + "AUTHENTICATION_BACKENDS": [ "awx.sso.backends.LDAPBackend", "awx.sso.backends.RADIUSBackend", "awx.sso.backends.TACACSPlusBackend", "social_core.backends.github.GithubTeamOAuth2", "django.contrib.auth.backends.ModelBackend" ], - "SOCIAL_AUTH_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_TEAM_MAP":null, - "SOCIAL_AUTH_USER_FIELDS":null, - "SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL":false, - "AUTH_LDAP_SERVER_URI":"ldap://ldap.example.com", - "AUTH_LDAP_BIND_DN":"cn=eng_user1", - "AUTH_LDAP_BIND_PASSWORD":"$encrypted$", - "AUTH_LDAP_START_TLS":false, - "AUTH_LDAP_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_USER_SEARCH":[], - "AUTH_LDAP_USER_DN_TEMPLATE":"uid=%(user)s,OU=Users,DC=example,DC=com", - "AUTH_LDAP_USER_ATTR_MAP":{}, - "AUTH_LDAP_GROUP_SEARCH":["DC=example,DC=com","SCOPE_SUBTREE","(objectClass=group)"], - "AUTH_LDAP_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_GROUP_TYPE_PARAMS":{"name_attr":"cn","member_attr":"member"}, - "AUTH_LDAP_REQUIRE_GROUP":"CN=Service Users,OU=Users,DC=example,DC=com", - "AUTH_LDAP_DENY_GROUP":null, - "AUTH_LDAP_USER_FLAGS_BY_GROUP":{"is_superuser":["cn=superusers"]}, - "AUTH_LDAP_ORGANIZATION_MAP":{}, - "AUTH_LDAP_TEAM_MAP":{}, - "AUTH_LDAP_1_SERVER_URI":"", - "AUTH_LDAP_1_BIND_DN":"", - "AUTH_LDAP_1_BIND_PASSWORD":"", - "AUTH_LDAP_1_START_TLS":true, - "AUTH_LDAP_1_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_1_USER_SEARCH":[], - "AUTH_LDAP_1_USER_DN_TEMPLATE":null, - "AUTH_LDAP_1_USER_ATTR_MAP":{}, - "AUTH_LDAP_1_GROUP_SEARCH":[], - "AUTH_LDAP_1_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_1_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"}, - "AUTH_LDAP_1_REQUIRE_GROUP":null, - "AUTH_LDAP_1_DENY_GROUP":"CN=Disabled1", - "AUTH_LDAP_1_USER_FLAGS_BY_GROUP":{}, - "AUTH_LDAP_1_ORGANIZATION_MAP":{}, - "AUTH_LDAP_1_TEAM_MAP":{}, - "AUTH_LDAP_2_SERVER_URI":"", - "AUTH_LDAP_2_BIND_DN":"", - "AUTH_LDAP_2_BIND_PASSWORD":"", - "AUTH_LDAP_2_START_TLS":false, - "AUTH_LDAP_2_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_2_USER_SEARCH":[], - "AUTH_LDAP_2_USER_DN_TEMPLATE":null, - "AUTH_LDAP_2_USER_ATTR_MAP":{}, - "AUTH_LDAP_2_GROUP_SEARCH":[], - "AUTH_LDAP_2_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_2_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"}, - "AUTH_LDAP_2_REQUIRE_GROUP":null, - "AUTH_LDAP_2_DENY_GROUP":"CN=Disabled2", - "AUTH_LDAP_2_USER_FLAGS_BY_GROUP":{}, - "AUTH_LDAP_2_ORGANIZATION_MAP":{}, - "AUTH_LDAP_2_TEAM_MAP":{}, - "AUTH_LDAP_3_SERVER_URI":"", - "AUTH_LDAP_3_BIND_DN":"", - "AUTH_LDAP_3_BIND_PASSWORD":"", - "AUTH_LDAP_3_START_TLS":false, - "AUTH_LDAP_3_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_3_USER_SEARCH":[], - "AUTH_LDAP_3_USER_DN_TEMPLATE":null, - "AUTH_LDAP_3_USER_ATTR_MAP":{}, - "AUTH_LDAP_3_GROUP_SEARCH":[], - "AUTH_LDAP_3_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_3_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"}, - "AUTH_LDAP_3_REQUIRE_GROUP":null, - "AUTH_LDAP_3_DENY_GROUP":null, - "AUTH_LDAP_3_USER_FLAGS_BY_GROUP":{}, - "AUTH_LDAP_3_ORGANIZATION_MAP":{}, - "AUTH_LDAP_3_TEAM_MAP":{}, - "AUTH_LDAP_4_SERVER_URI":"", - "AUTH_LDAP_4_BIND_DN":"", - "AUTH_LDAP_4_BIND_PASSWORD":"", - "AUTH_LDAP_4_START_TLS":false, - "AUTH_LDAP_4_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_4_USER_SEARCH":[], - "AUTH_LDAP_4_USER_DN_TEMPLATE":null, - "AUTH_LDAP_4_USER_ATTR_MAP":{}, - "AUTH_LDAP_4_GROUP_SEARCH":[], - "AUTH_LDAP_4_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_4_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"}, - "AUTH_LDAP_4_REQUIRE_GROUP":null, - "AUTH_LDAP_4_DENY_GROUP":null, - "AUTH_LDAP_4_USER_FLAGS_BY_GROUP":{}, - "AUTH_LDAP_4_ORGANIZATION_MAP":{}, - "AUTH_LDAP_4_TEAM_MAP":{}, - "AUTH_LDAP_5_SERVER_URI":"", - "AUTH_LDAP_5_BIND_DN":"", - "AUTH_LDAP_5_BIND_PASSWORD":"", - "AUTH_LDAP_5_START_TLS":false, - "AUTH_LDAP_5_CONNECTION_OPTIONS":{"OPT_REFERRALS":0,"OPT_NETWORK_TIMEOUT":30}, - "AUTH_LDAP_5_USER_SEARCH":[], - "AUTH_LDAP_5_USER_DN_TEMPLATE":null, - "AUTH_LDAP_5_USER_ATTR_MAP":{}, - "AUTH_LDAP_5_GROUP_SEARCH":[], - "AUTH_LDAP_5_GROUP_TYPE":"MemberDNGroupType", - "AUTH_LDAP_5_GROUP_TYPE_PARAMS":{"member_attr":"member","name_attr":"cn"}, - "AUTH_LDAP_5_REQUIRE_GROUP":null, - "AUTH_LDAP_5_DENY_GROUP":null, - "AUTH_LDAP_5_USER_FLAGS_BY_GROUP":{}, - "AUTH_LDAP_5_ORGANIZATION_MAP":{}, - "AUTH_LDAP_5_TEAM_MAP":{}, - "RADIUS_SERVER":"example.org", - "RADIUS_PORT":1812, - "RADIUS_SECRET":"$encrypted$", - "TACACSPLUS_HOST":"", - "TACACSPLUS_PORT":49, - "TACACSPLUS_SECRET":"", - "TACACSPLUS_SESSION_TIMEOUT":5, - "TACACSPLUS_AUTH_PROTOCOL":"ascii", - "SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL":"https://localhost:3000/sso/complete/google-oauth2/", - "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY":"", - "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET":"", - "SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS":[], - "SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS":{}, - "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP":null, - "SOCIAL_AUTH_GITHUB_CALLBACK_URL":"https://localhost:3000/sso/complete/github/", - "SOCIAL_AUTH_GITHUB_KEY":"", - "SOCIAL_AUTH_GITHUB_SECRET":"", - "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_GITHUB_TEAM_MAP":null, - "SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL":"https://localhost:3000/sso/complete/github-org/", - "SOCIAL_AUTH_GITHUB_ORG_KEY":"", - "SOCIAL_AUTH_GITHUB_ORG_SECRET":"", - "SOCIAL_AUTH_GITHUB_ORG_NAME":"", - "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP":null, - "SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL":"https://localhost:3000/sso/complete/github-team/", - "SOCIAL_AUTH_GITHUB_TEAM_KEY":"OAuth2 key (Client ID)", - "SOCIAL_AUTH_GITHUB_TEAM_SECRET":"$encrypted$", - "SOCIAL_AUTH_GITHUB_TEAM_ID":"team_id", - "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP":{}, - "SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP":{}, - "SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL":"https://localhost:3000/sso/complete/azuread-oauth2/", - "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY":"", - "SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET":"", - "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP":null, - "SAML_AUTO_CREATE_OBJECTS":true, - "SOCIAL_AUTH_SAML_CALLBACK_URL":"https://localhost:3000/sso/complete/saml/", - "SOCIAL_AUTH_SAML_METADATA_URL":"https://localhost:3000/sso/metadata/saml/", - "SOCIAL_AUTH_SAML_SP_ENTITY_ID":"", - "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT":"", - "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY":"", - "SOCIAL_AUTH_SAML_ORG_INFO":{}, - "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT":{}, - "SOCIAL_AUTH_SAML_SUPPORT_CONTACT":{}, - "SOCIAL_AUTH_SAML_ENABLED_IDPS":{}, - "SOCIAL_AUTH_SAML_SECURITY_CONFIG":{"requestedAuthnContext":false}, - "SOCIAL_AUTH_SAML_SP_EXTRA":null, - "SOCIAL_AUTH_SAML_EXTRA_DATA":null, - "SOCIAL_AUTH_SAML_ORGANIZATION_MAP":null, - "SOCIAL_AUTH_SAML_TEAM_MAP":null, - "SOCIAL_AUTH_SAML_ORGANIZATION_ATTR":{}, - "SOCIAL_AUTH_SAML_TEAM_ATTR":{}, - "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR":{}, - "SOCIAL_AUTH_OIDC_KEY":"", - "SOCIAL_AUTH_OIDC_SECRET":"", - "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT":"", - "SOCIAL_AUTH_OIDC_VERIFY_SSL":true, - "NAMED_URL_FORMATS":{ - "organizations":"", - "teams":"++", - "credential_types":"+", - "credentials":"+++++", - "notification_templates":"++", - "job_templates":"++", - "projects":"++", - "inventories":"++", - "hosts":"++++", - "groups":"++++", - "inventory_sources":"++++", - "inventory_scripts":"++", - "instance_groups":"", - "labels":"++", - "workflow_job_templates":"++", - "workflow_job_template_nodes":"++++", - "applications":"++", - "users":"", - "instances":"" + "SOCIAL_AUTH_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_TEAM_MAP": null, + "SOCIAL_AUTH_USER_FIELDS": null, + "SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL": false, + "AUTH_LDAP_SERVER_URI": "ldap://ldap.example.com", + "AUTH_LDAP_BIND_DN": "cn=eng_user1", + "AUTH_LDAP_BIND_PASSWORD": "$encrypted$", + "AUTH_LDAP_START_TLS": false, + "AUTH_LDAP_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 }, - "NAMED_URL_GRAPH_NODES":{ - "organizations":{"fields":["name"],"adj_list":[]}, - "teams":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "credential_types":{"fields":["name","kind"],"adj_list":[]}, - "credentials":{ - "fields":["name"], - "adj_list":[["credential_type","credential_types"],["organization","organizations"]] + "AUTH_LDAP_USER_SEARCH": [], + "AUTH_LDAP_USER_DN_TEMPLATE": "uid=%(user)s,OU=Users,DC=example,DC=com", + "AUTH_LDAP_USER_ATTR_MAP": {}, + "AUTH_LDAP_GROUP_SEARCH": [ + "DC=example,DC=com", + "SCOPE_SUBTREE", + "(objectClass=group)" + ], + "AUTH_LDAP_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_GROUP_TYPE_PARAMS": { "name_attr": "cn", "member_attr": "member" }, + "AUTH_LDAP_REQUIRE_GROUP": "CN=Service Users,OU=Users,DC=example,DC=com", + "AUTH_LDAP_DENY_GROUP": null, + "AUTH_LDAP_USER_FLAGS_BY_GROUP": { "is_superuser": ["cn=superusers"] }, + "AUTH_LDAP_ORGANIZATION_MAP": {}, + "AUTH_LDAP_TEAM_MAP": {}, + "AUTH_LDAP_1_SERVER_URI": "", + "AUTH_LDAP_1_BIND_DN": "", + "AUTH_LDAP_1_BIND_PASSWORD": "", + "AUTH_LDAP_1_START_TLS": true, + "AUTH_LDAP_1_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 + }, + "AUTH_LDAP_1_USER_SEARCH": [], + "AUTH_LDAP_1_USER_DN_TEMPLATE": null, + "AUTH_LDAP_1_USER_ATTR_MAP": {}, + "AUTH_LDAP_1_GROUP_SEARCH": [], + "AUTH_LDAP_1_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_1_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_1_REQUIRE_GROUP": null, + "AUTH_LDAP_1_DENY_GROUP": "CN=Disabled1", + "AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_1_ORGANIZATION_MAP": {}, + "AUTH_LDAP_1_TEAM_MAP": {}, + "AUTH_LDAP_2_SERVER_URI": "", + "AUTH_LDAP_2_BIND_DN": "", + "AUTH_LDAP_2_BIND_PASSWORD": "", + "AUTH_LDAP_2_START_TLS": false, + "AUTH_LDAP_2_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 + }, + "AUTH_LDAP_2_USER_SEARCH": [], + "AUTH_LDAP_2_USER_DN_TEMPLATE": null, + "AUTH_LDAP_2_USER_ATTR_MAP": {}, + "AUTH_LDAP_2_GROUP_SEARCH": [], + "AUTH_LDAP_2_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_2_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_2_REQUIRE_GROUP": null, + "AUTH_LDAP_2_DENY_GROUP": "CN=Disabled2", + "AUTH_LDAP_2_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_2_ORGANIZATION_MAP": {}, + "AUTH_LDAP_2_TEAM_MAP": {}, + "AUTH_LDAP_3_SERVER_URI": "", + "AUTH_LDAP_3_BIND_DN": "", + "AUTH_LDAP_3_BIND_PASSWORD": "", + "AUTH_LDAP_3_START_TLS": false, + "AUTH_LDAP_3_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 + }, + "AUTH_LDAP_3_USER_SEARCH": [], + "AUTH_LDAP_3_USER_DN_TEMPLATE": null, + "AUTH_LDAP_3_USER_ATTR_MAP": {}, + "AUTH_LDAP_3_GROUP_SEARCH": [], + "AUTH_LDAP_3_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_3_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_3_REQUIRE_GROUP": null, + "AUTH_LDAP_3_DENY_GROUP": null, + "AUTH_LDAP_3_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_3_ORGANIZATION_MAP": {}, + "AUTH_LDAP_3_TEAM_MAP": {}, + "AUTH_LDAP_4_SERVER_URI": "", + "AUTH_LDAP_4_BIND_DN": "", + "AUTH_LDAP_4_BIND_PASSWORD": "", + "AUTH_LDAP_4_START_TLS": false, + "AUTH_LDAP_4_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 + }, + "AUTH_LDAP_4_USER_SEARCH": [], + "AUTH_LDAP_4_USER_DN_TEMPLATE": null, + "AUTH_LDAP_4_USER_ATTR_MAP": {}, + "AUTH_LDAP_4_GROUP_SEARCH": [], + "AUTH_LDAP_4_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_4_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_4_REQUIRE_GROUP": null, + "AUTH_LDAP_4_DENY_GROUP": null, + "AUTH_LDAP_4_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_4_ORGANIZATION_MAP": {}, + "AUTH_LDAP_4_TEAM_MAP": {}, + "AUTH_LDAP_5_SERVER_URI": "", + "AUTH_LDAP_5_BIND_DN": "", + "AUTH_LDAP_5_BIND_PASSWORD": "", + "AUTH_LDAP_5_START_TLS": false, + "AUTH_LDAP_5_CONNECTION_OPTIONS": { + "OPT_REFERRALS": 0, + "OPT_NETWORK_TIMEOUT": 30 + }, + "AUTH_LDAP_5_USER_SEARCH": [], + "AUTH_LDAP_5_USER_DN_TEMPLATE": null, + "AUTH_LDAP_5_USER_ATTR_MAP": {}, + "AUTH_LDAP_5_GROUP_SEARCH": [], + "AUTH_LDAP_5_GROUP_TYPE": "MemberDNGroupType", + "AUTH_LDAP_5_GROUP_TYPE_PARAMS": { + "member_attr": "member", + "name_attr": "cn" + }, + "AUTH_LDAP_5_REQUIRE_GROUP": null, + "AUTH_LDAP_5_DENY_GROUP": null, + "AUTH_LDAP_5_USER_FLAGS_BY_GROUP": {}, + "AUTH_LDAP_5_ORGANIZATION_MAP": {}, + "AUTH_LDAP_5_TEAM_MAP": {}, + "RADIUS_SERVER": "example.org", + "RADIUS_PORT": 1812, + "RADIUS_SECRET": "$encrypted$", + "TACACSPLUS_HOST": "", + "TACACSPLUS_PORT": 49, + "TACACSPLUS_SECRET": "", + "TACACSPLUS_SESSION_TIMEOUT": 5, + "TACACSPLUS_AUTH_PROTOCOL": "ascii", + "SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL": "https://localhost:3000/sso/complete/google-oauth2/", + "SOCIAL_AUTH_GOOGLE_OAUTH2_KEY": "", + "SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET": "", + "SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS": [], + "SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS": {}, + "SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP": null, + "SOCIAL_AUTH_GITHUB_CALLBACK_URL": "https://localhost:3000/sso/complete/github/", + "SOCIAL_AUTH_GITHUB_KEY": "", + "SOCIAL_AUTH_GITHUB_SECRET": "", + "SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_GITHUB_TEAM_MAP": null, + "SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL": "https://localhost:3000/sso/complete/github-org/", + "SOCIAL_AUTH_GITHUB_ORG_KEY": "", + "SOCIAL_AUTH_GITHUB_ORG_SECRET": "", + "SOCIAL_AUTH_GITHUB_ORG_NAME": "", + "SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP": null, + "SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL": "https://localhost:3000/sso/complete/github-team/", + "SOCIAL_AUTH_GITHUB_TEAM_KEY": "OAuth2 key (Client ID)", + "SOCIAL_AUTH_GITHUB_TEAM_SECRET": "$encrypted$", + "SOCIAL_AUTH_GITHUB_TEAM_ID": "team_id", + "SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP": {}, + "SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP": {}, + "SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL": "https://localhost:3000/sso/complete/azuread-oauth2/", + "SOCIAL_AUTH_AZUREAD_OAUTH2_KEY": "", + "SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET": "", + "SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP": null, + "SAML_AUTO_CREATE_OBJECTS": true, + "SOCIAL_AUTH_SAML_CALLBACK_URL": "https://localhost:3000/sso/complete/saml/", + "SOCIAL_AUTH_SAML_METADATA_URL": "https://localhost:3000/sso/metadata/saml/", + "SOCIAL_AUTH_SAML_SP_ENTITY_ID": "", + "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": "", + "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": "", + "SOCIAL_AUTH_SAML_ORG_INFO": {}, + "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {}, + "SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {}, + "SOCIAL_AUTH_SAML_ENABLED_IDPS": {}, + "SOCIAL_AUTH_SAML_SECURITY_CONFIG": { "requestedAuthnContext": false }, + "SOCIAL_AUTH_SAML_SP_EXTRA": null, + "SOCIAL_AUTH_SAML_EXTRA_DATA": null, + "SOCIAL_AUTH_SAML_ORGANIZATION_MAP": null, + "SOCIAL_AUTH_SAML_TEAM_MAP": null, + "SOCIAL_AUTH_SAML_ORGANIZATION_ATTR": {}, + "SOCIAL_AUTH_SAML_TEAM_ATTR": {}, + "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": {}, + "SOCIAL_AUTH_OIDC_KEY": "", + "SOCIAL_AUTH_OIDC_SECRET": "", + "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": "", + "SOCIAL_AUTH_OIDC_VERIFY_SSL": true, + "NAMED_URL_FORMATS": { + "organizations": "", + "teams": "++", + "credential_types": "+", + "credentials": "+++++", + "notification_templates": "++", + "job_templates": "++", + "projects": "++", + "inventories": "++", + "hosts": "++++", + "groups": "++++", + "inventory_sources": "++++", + "inventory_scripts": "++", + "instance_groups": "", + "labels": "++", + "workflow_job_templates": "++", + "workflow_job_template_nodes": "++++", + "applications": "++", + "users": "", + "instances": "" + }, + "LOCAL_PASSWORD_MIN_LENGTH": 0, + "LOCAL_PASSWORD_MIN_DIGITS": 0, + "LOCAL_PASSWORD_MIN_UPPER": 0, + "LOCAL_PASSWORD_MIN_SPECIAL": 0, + "NAMED_URL_GRAPH_NODES": { + "organizations": { "fields": ["name"], "adj_list": [] }, + "teams": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] }, - "notification_templates":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "job_templates":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "projects":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "inventories":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "hosts":{"fields":["name"],"adj_list":[["inventory","inventories"]]}, - "groups":{"fields":["name"],"adj_list":[["inventory","inventories"]]}, - "inventory_sources":{"fields":["name"],"adj_list":[["inventory","inventories"]]}, - "inventory_scripts":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "instance_groups":{"fields":["name"],"adj_list":[]}, - "labels":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "workflow_job_templates":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "workflow_job_template_nodes":{ - "fields":["identifier"], - "adj_list":[["workflow_job_template","workflow_job_templates"]] + "credential_types": { "fields": ["name", "kind"], "adj_list": [] }, + "credentials": { + "fields": ["name"], + "adj_list": [ + ["credential_type", "credential_types"], + ["organization", "organizations"] + ] }, - "applications":{"fields":["name"],"adj_list":[["organization","organizations"]]}, - "users":{"fields":["username"],"adj_list":[]}, - "instances":{"fields":["hostname"],"adj_list":[]} + "notification_templates": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "job_templates": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "projects": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "inventories": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "hosts": { "fields": ["name"], "adj_list": [["inventory", "inventories"]] }, + "groups": { + "fields": ["name"], + "adj_list": [["inventory", "inventories"]] + }, + "inventory_sources": { + "fields": ["name"], + "adj_list": [["inventory", "inventories"]] + }, + "inventory_scripts": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "instance_groups": { "fields": ["name"], "adj_list": [] }, + "labels": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "workflow_job_templates": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "workflow_job_template_nodes": { + "fields": ["identifier"], + "adj_list": [["workflow_job_template", "workflow_job_templates"]] + }, + "applications": { + "fields": ["name"], + "adj_list": [["organization", "organizations"]] + }, + "users": { "fields": ["username"], "adj_list": [] }, + "instances": { "fields": ["hostname"], "adj_list": [] } }, "DEFAULT_EXECUTION_ENVIRONMENT": 1, "AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false, From 11d5e5c7d49b1aa997f84c7b4599dc690c19f41d Mon Sep 17 00:00:00 2001 From: Joe Garcia Date: Thu, 13 Apr 2023 13:11:37 -0400 Subject: [PATCH 09/14] Fixes #13402 allow user defined key retrieval from CYBR (#13411) * Fixed #13402 allow user defined key retrieval from CYBR * Add default value to object_property * Raise ValueError if object_property not in response * Raise KeyError instead of ValueError --- awx/main/credential_plugins/aim.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/awx/main/credential_plugins/aim.py b/awx/main/credential_plugins/aim.py index e72fe285f4..048bd1b324 100644 --- a/awx/main/credential_plugins/aim.py +++ b/awx/main/credential_plugins/aim.py @@ -54,6 +54,12 @@ aim_inputs = { 'help_text': _('Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123'), }, {'id': 'object_query_format', 'label': _('Object Query Format'), 'type': 'string', 'default': 'Exact', 'choices': ['Exact', 'Regexp']}, + { + 'id': 'object_property', + 'label': _('Object Property'), + 'type': 'string', + 'help_text': _('The property of the object to return. Default: Content Ex: Username, Address, etc.'), + }, { 'id': 'reason', 'label': _('Reason'), @@ -74,6 +80,7 @@ def aim_backend(**kwargs): app_id = kwargs['app_id'] object_query = kwargs['object_query'] object_query_format = kwargs['object_query_format'] + object_property = kwargs.get('object_property', '') reason = kwargs.get('reason', None) if webservice_id == '': webservice_id = 'AIMWebService' @@ -98,7 +105,18 @@ def aim_backend(**kwargs): allow_redirects=False, ) raise_for_status(res) - return res.json()['Content'] + # CCP returns the property name capitalized, username is camel case + # so we need to handle that case + if object_property == '': + object_property = 'Content' + elif object_property.lower() == 'username': + object_property = 'UserName' + elif object_property not in res: + raise KeyError('Property {} not found in object'.format(object_property)) + else: + object_property = object_property.capitalize() + + return res.json()[object_property] aim_plugin = CredentialPlugin('CyberArk Central Credential Provider Lookup', inputs=aim_inputs, backend=aim_backend) From c1455ee12501e3e5a5942450f3cfa0e9aaa56a19 Mon Sep 17 00:00:00 2001 From: Dien Nguyen Date: Thu, 13 Apr 2023 14:36:38 -0400 Subject: [PATCH 10/14] bugfix: add scm_branch to optional_args for workflow_launch (#13254) * add scm_branch to optional_args * add in limits * Update workflow_launch.py remove json from import to pass linting. --------- Co-authored-by: dien nguyen Co-authored-by: Jessica Steurer <70719005+jay-steurer@users.noreply.github.com> --- awx_collection/plugins/modules/workflow_launch.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/awx_collection/plugins/modules/workflow_launch.py b/awx_collection/plugins/modules/workflow_launch.py index 1613e4fa8b..afbf9ca8d6 100644 --- a/awx_collection/plugins/modules/workflow_launch.py +++ b/awx_collection/plugins/modules/workflow_launch.py @@ -91,7 +91,6 @@ EXAMPLES = ''' ''' from ..module_utils.controller_api import ControllerAPIModule -import json def main(): @@ -116,15 +115,18 @@ def main(): name = module.params.get('name') organization = module.params.get('organization') inventory = module.params.get('inventory') - optional_args['limit'] = module.params.get('limit') wait = module.params.get('wait') interval = module.params.get('interval') timeout = module.params.get('timeout') - # Special treatment of extra_vars parameter - extra_vars = module.params.get('extra_vars') - if extra_vars is not None: - optional_args['extra_vars'] = json.dumps(extra_vars) + for field_name in ( + 'limit', + 'extra_vars', + 'scm_branch', + ): + field_val = module.params.get(field_name) + if field_val is not None: + optional_args[field_name] = field_val # Create a datastructure to pass into our job launch post_data = {} From 8719648ff56e06a1cbd6eee14d06f04323e8b787 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 13 Apr 2023 15:02:08 -0400 Subject: [PATCH 11/14] Adding tacacs+ container for testing --- Makefile | 6 +++- tools/docker-compose/README.md | 24 ++++++++++++++ tools/docker-compose/ansible/plumb_tacacs.yml | 32 +++++++++++++++++++ .../sources/templates/docker-compose.yml.j2 | 8 +++++ .../templates/tacacsplus_settings.json.j2 | 7 ++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tools/docker-compose/ansible/plumb_tacacs.yml create mode 100644 tools/docker-compose/ansible/templates/tacacsplus_settings.json.j2 diff --git a/Makefile b/Makefile index 9afc7c7440..7664a32e24 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,8 @@ SPLUNK ?= false PROMETHEUS ?= false # If set to true docker-compose will also start a grafana instance GRAFANA ?= false +# If set to true docker-compose will also start a tacacs+ instance +TACACS ?= false VENV_BASE ?= /var/lib/awx/venv @@ -519,7 +521,9 @@ docker-compose-sources: .git/hooks/pre-commit -e enable_ldap=$(LDAP) \ -e enable_splunk=$(SPLUNK) \ -e enable_prometheus=$(PROMETHEUS) \ - -e enable_grafana=$(GRAFANA) $(EXTRA_SOURCES_ANSIBLE_OPTS) + -e enable_grafana=$(GRAFANA) \ + -e enable_tacacs=$(TACACS) \ + $(EXTRA_SOURCES_ANSIBLE_OPTS) docker-compose: awx/projects docker-compose-sources $(DOCKER_COMPOSE) -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_OPTS) up $(COMPOSE_UP_OPTS) --remove-orphans diff --git a/tools/docker-compose/README.md b/tools/docker-compose/README.md index b450398ee0..e071f33923 100644 --- a/tools/docker-compose/README.md +++ b/tools/docker-compose/README.md @@ -244,6 +244,7 @@ $ make docker-compose - [SAML and OIDC Integration](#saml-and-oidc-integration) - [OpenLDAP Integration](#openldap-integration) - [Splunk Integration](#splunk-integration) +- [tacacs+ Integration](#tacacs+-integration) ### Start a Shell @@ -472,6 +473,29 @@ ansible-playbook tools/docker-compose/ansible/plumb_splunk.yml Once the playbook is done running Splunk should now be setup in your development environment. You can log into the admin console (see above for username/password) and click on "Searching and Reporting" in the left hand navigation. In the search box enter `source="http:tower_logging_collections"` and click search. +### - tacacs+ Integration + +tacacs+ is an networking protocol that provides external authentication which can be used with AWX. This section describes how to build a reference tacacs+ instance and plumb it with your AWX for testing purposes. + +First, be sure that you have the awx.awx collection installed by running `make install_collection`. + +Anytime you want to run a tacacs+ instance alongside AWX we can start docker-compose with the TACACS option to get a containerized instance with the command: +```bash +TACACS=true make docker-compose +``` + +Once the containers come up a new port (49) should be exposed and the tacacs+ server should be running on those ports. + +Now we are ready to configure and plumb tacacs+ with AWX. To do this we have provided a playbook which will: +* Backup and configure the tacacsplus adapter in AWX. NOTE: this will back up your existing settings but the password fields can not be backed up through the API, you need a DB backup to recover this. + +```bash +export CONTROLLER_USERNAME= +export CONTROLLER_PASSWORD= +ansible-playbook tools/docker-compose/ansible/plumb_tacacs.yml +``` + +Once the playbook is done running tacacs+ should now be setup in your development environment. This server has the accounts listed on https://hub.docker.com/r/dchidell/docker-tacacs ### Prometheus and Grafana integration diff --git a/tools/docker-compose/ansible/plumb_tacacs.yml b/tools/docker-compose/ansible/plumb_tacacs.yml new file mode 100644 index 0000000000..c7dcbe5e22 --- /dev/null +++ b/tools/docker-compose/ansible/plumb_tacacs.yml @@ -0,0 +1,32 @@ +--- +- name: Plumb a tacacs+ instance + hosts: localhost + connection: local + gather_facts: False + vars: + awx_host: "https://localhost:8043" + tasks: + - name: Load existing and new tacacs+ settings + set_fact: + existing_tacacs: "{{ lookup('awx.awx.controller_api', 'settings/tacacsplus', host=awx_host, verify_ssl=false) }}" + new_tacacs: "{{ lookup('template', 'tacacsplus_settings.json.j2') }}" + + - name: Display existing tacacs+ configuration + debug: + msg: + - "Here is your existing tacacsplus configuration for reference:" + - "{{ existing_tacacs }}" + + - pause: + prompt: "Continuing to run this will replace your existing tacacs settings (displayed above). They will all be captured. Be sure that is backed up before continuing" + + - name: Write out the existing content + copy: + dest: "../_sources/existing_tacacsplus_adapter_settings.json" + content: "{{ existing_tacacs }}" + + - name: Configure AWX tacacs+ adapter + awx.awx.settings: + settings: "{{ new_tacacs }}" + controller_host: "{{ awx_host }}" + validate_certs: False diff --git a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 index 7badd37181..6bc49347b2 100644 --- a/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 +++ b/tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2 @@ -174,6 +174,14 @@ services: - prometheus depends_on: - prometheus +{% endif %} +{% if enable_tacacs|bool %} + tacacs: + image: dchidell/docker-tacacs + container_name: tools_tacacs_1 + hostname: tacacs + ports: + - "49:49" {% endif %} # A useful container that simply passes through log messages to the console # helpful for testing awx/tower logging diff --git a/tools/docker-compose/ansible/templates/tacacsplus_settings.json.j2 b/tools/docker-compose/ansible/templates/tacacsplus_settings.json.j2 new file mode 100644 index 0000000000..fe9dd8c391 --- /dev/null +++ b/tools/docker-compose/ansible/templates/tacacsplus_settings.json.j2 @@ -0,0 +1,7 @@ +{ + "TACACSPLUS_HOST": "tacacs", + "TACACSPLUS_PORT": 49, + "TACACSPLUS_SECRET": "ciscotacacskey", + "TACACSPLUS_SESSION_TIMEOUT": 5, + "TACACSPLUS_AUTH_PROTOCOL": "ascii" +} From 7cdf471894925e67740eca75901d56f49e1c9d40 Mon Sep 17 00:00:00 2001 From: John Westcott IV <32551173+john-westcott-iv@users.noreply.github.com> Date: Thu, 13 Apr 2023 16:14:06 -0400 Subject: [PATCH 12/14] Fix sat instance var (#13851) * add the fallback satellite_instance_var_id * Removing unnecessary whitespace --------- Co-authored-by: Nikhil Jain --- awx/settings/defaults.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 7e1cba1c64..b72147c91f 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -734,10 +734,10 @@ CONTROLLER_INSTANCE_ID_VAR = 'remote_tower_id' # --------------------- # ----- Foreman ----- # --------------------- -SATELLITE6_ENABLED_VAR = 'foreman_enabled' +SATELLITE6_ENABLED_VAR = 'foreman_enabled,foreman.enabled' SATELLITE6_ENABLED_VALUE = 'True' SATELLITE6_EXCLUDE_EMPTY_GROUPS = True -SATELLITE6_INSTANCE_ID_VAR = 'foreman_id' +SATELLITE6_INSTANCE_ID_VAR = 'foreman_id,foreman.id' # SATELLITE6_GROUP_PREFIX and SATELLITE6_GROUP_PATTERNS defined in source vars # ---------------- From 342e9197b8934050452279a8414def0d4ace1409 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Thu, 13 Apr 2023 22:36:36 -0400 Subject: [PATCH 13/14] Customize application_name for different connections in dispatcher service (#13074) * Introduce new method in settings, import in-line w NOQA mark * Further refine the app_name to use shorter service names like dispatcher * Clean up listener logic, change some names --- awx/main/dispatch/__init__.py | 11 +++++++---- awx/main/dispatch/periodic.py | 4 ++++ awx/main/dispatch/worker/base.py | 2 ++ awx/main/utils/db.py | 7 +++++++ awx/settings/application_name.py | 31 +++++++++++++++++++++++++++++++ awx/settings/development.py | 7 +++++-- awx/settings/production.py | 8 +++++--- 7 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 awx/settings/application_name.py diff --git a/awx/main/dispatch/__init__.py b/awx/main/dispatch/__init__.py index 2029f540c2..e3cd64ce6d 100644 --- a/awx/main/dispatch/__init__.py +++ b/awx/main/dispatch/__init__.py @@ -4,6 +4,8 @@ import select from contextlib import contextmanager +from awx.settings.application_name import get_application_name + from django.conf import settings from django.db import connection as pg_connection @@ -83,10 +85,11 @@ def pg_bus_conn(new_connection=False): ''' if new_connection: - conf = settings.DATABASES['default'] - conn = psycopg2.connect( - dbname=conf['NAME'], host=conf['HOST'], user=conf['USER'], password=conf['PASSWORD'], port=conf['PORT'], **conf.get("OPTIONS", {}) - ) + conf = settings.DATABASES['default'].copy() + conf['OPTIONS'] = conf.get('OPTIONS', {}).copy() + # Modify the application name to distinguish from other connections the process might use + conf['OPTIONS']['application_name'] = get_application_name(settings.CLUSTER_HOST_ID, function='listener') + conn = psycopg2.connect(dbname=conf['NAME'], host=conf['HOST'], user=conf['USER'], password=conf['PASSWORD'], port=conf['PORT'], **conf['OPTIONS']) # Django connection.cursor().connection doesn't have autocommit=True on by default conn.set_session(autocommit=True) else: diff --git a/awx/main/dispatch/periodic.py b/awx/main/dispatch/periodic.py index e3e7da5db9..aac8427b5a 100644 --- a/awx/main/dispatch/periodic.py +++ b/awx/main/dispatch/periodic.py @@ -10,6 +10,7 @@ from django_guid import set_guid from django_guid.utils import generate_guid from awx.main.dispatch.worker import TaskWorker +from awx.main.utils.db import set_connection_name logger = logging.getLogger('awx.main.dispatch.periodic') @@ -21,6 +22,9 @@ class Scheduler(Scheduler): def run(): ppid = os.getppid() logger.warning('periodic beat started') + + set_connection_name('periodic') # set application_name to distinguish from other dispatcher processes + while True: if os.getppid() != ppid: # if the parent PID changes, this process has been orphaned diff --git a/awx/main/dispatch/worker/base.py b/awx/main/dispatch/worker/base.py index a7b0d83e95..9a9d4c803c 100644 --- a/awx/main/dispatch/worker/base.py +++ b/awx/main/dispatch/worker/base.py @@ -18,6 +18,7 @@ from django.conf import settings from awx.main.dispatch.pool import WorkerPool from awx.main.dispatch import pg_bus_conn from awx.main.utils.common import log_excess_runtime +from awx.main.utils.db import set_connection_name if 'run_callback_receiver' in sys.argv: logger = logging.getLogger('awx.main.commands.run_callback_receiver') @@ -219,6 +220,7 @@ class BaseWorker(object): def work_loop(self, queue, finished, idx, *args): ppid = os.getppid() signal_handler = WorkerSignalHandler() + set_connection_name('worker') # set application_name to distinguish from other dispatcher processes while not signal_handler.kill_now: # if the parent PID changes, this process has been orphaned # via e.g., segfault or sigkill, we should exit too diff --git a/awx/main/utils/db.py b/awx/main/utils/db.py index 5574d4ea91..4117c5274c 100644 --- a/awx/main/utils/db.py +++ b/awx/main/utils/db.py @@ -3,6 +3,9 @@ from itertools import chain +from awx.settings.application_name import set_application_name +from django.conf import settings + def get_all_field_names(model): # Implements compatibility with _meta.get_all_field_names @@ -18,3 +21,7 @@ def get_all_field_names(model): ) ) ) + + +def set_connection_name(function): + set_application_name(settings.DATABASES, settings.CLUSTER_HOST_ID, function=function) diff --git a/awx/settings/application_name.py b/awx/settings/application_name.py new file mode 100644 index 0000000000..25c68acfd3 --- /dev/null +++ b/awx/settings/application_name.py @@ -0,0 +1,31 @@ +import os +import sys + + +def get_service_name(argv): + ''' + Return best-effort guess as to the name of this service + ''' + for arg in argv: + if arg == '-m': + continue + if 'python' in arg: + continue + if 'manage' in arg: + continue + if arg.startswith('run_'): + return arg[len('run_') :] + return arg + + +def get_application_name(CLUSTER_HOST_ID, function=''): + if function: + function = f'_{function}' + return f'awx-{os.getpid()}-{get_service_name(sys.argv)}{function}-{CLUSTER_HOST_ID}'[:63] + + +def set_application_name(DATABASES, CLUSTER_HOST_ID, function=''): + if 'sqlite3' in DATABASES['default']['ENGINE']: + return + options_dict = DATABASES['default'].setdefault('OPTIONS', dict()) + options_dict['application_name'] = get_application_name(CLUSTER_HOST_ID, function) diff --git a/awx/settings/development.py b/awx/settings/development.py index 1be4b72956..b8b911b07c 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -105,8 +105,11 @@ AWX_CALLBACK_PROFILE = True AWX_DISABLE_TASK_MANAGERS = False # ======================!!!!!!! FOR DEVELOPMENT ONLY !!!!!!!================================= -if 'sqlite3' not in DATABASES['default']['ENGINE']: # noqa - DATABASES['default'].setdefault('OPTIONS', dict()).setdefault('application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63]) # noqa +from .application_name import set_application_name + +set_application_name(DATABASES, CLUSTER_HOST_ID) + +del set_application_name # If any local_*.py files are present in awx/settings/, use them to override # default settings for development. If not present, we can still run using diff --git a/awx/settings/production.py b/awx/settings/production.py index 3dce95deb0..4f25d274b1 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -100,6 +100,8 @@ except IOError: # The below runs AFTER all of the custom settings are imported. -DATABASES.setdefault('default', dict()).setdefault('OPTIONS', dict()).setdefault( - 'application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63] # NOQA -) # noqa +from .application_name import set_application_name + +set_application_name(DATABASES, CLUSTER_HOST_ID) # NOQA + +del set_application_name From 36c9c9cdc476ebc8d968cec2560c1e73b5d0ab4a Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Fri, 14 Apr 2023 09:51:53 -0400 Subject: [PATCH 14/14] Move integration tests to be consistent with the rest --- .../integration/targets/bulk_host_create/{ => tasks}/main.yml | 0 .../integration/targets/bulk_job_launch/{ => tasks}/main.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename awx_collection/tests/integration/targets/bulk_host_create/{ => tasks}/main.yml (100%) rename awx_collection/tests/integration/targets/bulk_job_launch/{ => tasks}/main.yml (100%) diff --git a/awx_collection/tests/integration/targets/bulk_host_create/main.yml b/awx_collection/tests/integration/targets/bulk_host_create/tasks/main.yml similarity index 100% rename from awx_collection/tests/integration/targets/bulk_host_create/main.yml rename to awx_collection/tests/integration/targets/bulk_host_create/tasks/main.yml diff --git a/awx_collection/tests/integration/targets/bulk_job_launch/main.yml b/awx_collection/tests/integration/targets/bulk_job_launch/tasks/main.yml similarity index 100% rename from awx_collection/tests/integration/targets/bulk_job_launch/main.yml rename to awx_collection/tests/integration/targets/bulk_job_launch/tasks/main.yml