Merge branch 'devel' into collection-existential-state-for-credential-module

This commit is contained in:
Matthew Fernandez 2023-04-18 09:51:54 -06:00 committed by GitHub
commit d57f549a4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1315 additions and 497 deletions

View File

@ -6,6 +6,10 @@ on:
- opened
- reopened
permissions:
contents: read # to fetch code
issues: write # to label issues
jobs:
triage:
runs-on: ubuntu-latest

View File

@ -7,6 +7,10 @@ on:
- reopened
- synchronize
permissions:
contents: read # to determine modified files (actions/labeler)
pull-requests: write # to add labels to PRs (actions/labeler)
jobs:
triage:
runs-on: ubuntu-latest

View File

@ -8,6 +8,9 @@ on:
release:
types: [published]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
promote:
if: endsWith(github.repository, '/awx')

View File

@ -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

View File

@ -25,6 +25,7 @@ __all__ = [
'UserPermission',
'IsSystemAdminOrAuditor',
'WorkflowApprovalPermission',
'AnalyticsPermission',
]
@ -250,3 +251,16 @@ class IsSystemAdminOrAuditor(permissions.BasePermission):
class WebhookKeyPermission(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.can_access(view.model, 'admin', obj, request.data)
class AnalyticsPermission(permissions.BasePermission):
"""
Allows GET/POST/OPTIONS to system admins and system auditors.
"""
def has_permission(self, request, view):
if not (request.user and request.user.is_authenticated):
return False
if request.method in ["GET", "POST", "OPTIONS"]:
return request.user.is_superuser or request.user.is_system_auditor
return request.user.is_superuser

View File

@ -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):

View File

@ -7,10 +7,9 @@ from django.utils.translation import gettext_lazy as _
from django.utils import translation
from awx.api.generics import APIView, Response
from awx.api.permissions import IsSystemAdminOrAuditor
from awx.api.permissions import AnalyticsPermission
from awx.api.versioning import reverse
from awx.main.utils import get_awx_version
from rest_framework.permissions import AllowAny
from rest_framework import status
from collections import OrderedDict
@ -43,7 +42,7 @@ class GetNotAllowedMixin(object):
class AnalyticsRootView(APIView):
permission_classes = (AllowAny,)
permission_classes = (AnalyticsPermission,)
name = _('Automation Analytics')
swagger_topic = 'Automation Analytics'
@ -99,7 +98,7 @@ class AnalyticsGenericView(APIView):
return Response(response.json(), status=response.status_code)
"""
permission_classes = (IsSystemAdminOrAuditor,)
permission_classes = (AnalyticsPermission,)
@staticmethod
def _request_headers(request):

View File

@ -131,7 +131,7 @@ def _identify_lower(key, since, until, last_gather):
return lower, last_entries
@register('config', '1.5', description=_('General platform configuration.'))
@register('config', '1.6', description=_('General platform configuration.'))
def config(since, **kwargs):
license_info = get_license()
install_type = 'traditional'
@ -155,10 +155,13 @@ def config(since, **kwargs):
'subscription_name': license_info.get('subscription_name'),
'sku': license_info.get('sku'),
'support_level': license_info.get('support_level'),
'usage': license_info.get('usage'),
'product_name': license_info.get('product_name'),
'valid_key': license_info.get('valid_key'),
'satellite': license_info.get('satellite'),
'pool_id': license_info.get('pool_id'),
'subscription_id': license_info.get('subscription_id'),
'account_number': license_info.get('account_number'),
'current_instances': license_info.get('current_instances'),
'automated_instances': license_info.get('automated_instances'),
'automated_since': license_info.get('automated_since'),

View File

@ -298,11 +298,13 @@ class Metrics:
try:
current_time = time.time()
if current_time - self.previous_send_metrics.decode(self.conn) > self.send_metrics_interval:
serialized_metrics = self.serialize_local_metrics()
payload = {
'instance': self.instance_name,
'metrics': self.serialize_local_metrics(),
'metrics': serialized_metrics,
}
# store the serialized data locally as well, so that load_other_metrics will read it
self.conn.set(root_key + '_instance_' + self.instance_name, serialized_metrics)
emit_channel_notification("metrics", payload)
self.previous_send_metrics.set(current_time)

View File

@ -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)

View File

@ -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']], # fmt: skip
)

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -98,6 +98,7 @@ class Command(BaseCommand):
try:
executor = MigrationExecutor(connection)
migrating = bool(executor.migration_plan(executor.loader.graph.leaf_nodes()))
connection.close() # Because of async nature, main loop will use new connection, so close this
except Exception as exc:
logger.warning(f'Error on startup of run_wsrelay (error: {exc}), retry in 10s...')
time.sleep(10)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):
'''

View File

@ -218,3 +218,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

View File

@ -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):

View File

@ -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)

View File

@ -170,6 +170,8 @@ class Licenser(object):
license.setdefault('sku', sub['pool']['productId'])
license.setdefault('subscription_name', sub['pool']['productName'])
license.setdefault('subscription_id', sub['pool']['subscriptionId'])
license.setdefault('account_number', sub['pool']['accountNumber'])
license.setdefault('pool_id', sub['pool']['id'])
license.setdefault('product_name', sub['pool']['productName'])
license.setdefault('valid_key', True)
@ -185,6 +187,14 @@ class Licenser(object):
license['instance_count'] = license.get('instance_count', 0) + instances
license['subscription_name'] = re.sub(r'[\d]* Managed Nodes', '%d Managed Nodes' % license['instance_count'], license['subscription_name'])
license['support_level'] = ''
license['usage'] = ''
for attr in sub['pool'].get('productAttributes', []):
if attr.get('name') == 'support_level':
license['support_level'] = attr.get('value')
elif attr.get('name') == 'usage':
license['usage'] = attr.get('value')
if not license:
logger.error("No valid subscriptions found in manifest")
self._attrs.update(license)
@ -277,7 +287,10 @@ class Licenser(object):
license['productId'] = sub['product_id']
license['quantity'] = int(sub['quantity'])
license['support_level'] = sub['support_level']
license['usage'] = sub['usage']
license['subscription_name'] = sub['name']
license['subscriptionId'] = sub['subscription_id']
license['accountNumber'] = sub['account_number']
license['id'] = sub['upstream_pool_id']
license['endDate'] = sub['end_date']
license['productName'] = "Red Hat Ansible Automation"
@ -304,7 +317,7 @@ class Licenser(object):
def generate_license_options_from_entitlements(self, json):
from dateutil.parser import parse
ValidSub = collections.namedtuple('ValidSub', 'sku name support_level end_date trial quantity pool_id satellite')
ValidSub = collections.namedtuple('ValidSub', 'sku name support_level end_date trial quantity pool_id satellite subscription_id account_number usage')
valid_subs = []
for sub in json:
satellite = sub.get('satellite')
@ -333,15 +346,23 @@ class Licenser(object):
sku = sub['productId']
trial = sku.startswith('S') # i.e.,, SER/SVC
support_level = ''
usage = ''
pool_id = sub['id']
subscription_id = sub['subscriptionId']
account_number = sub['accountNumber']
if satellite:
support_level = sub['support_level']
usage = sub['usage']
else:
for attr in sub.get('productAttributes', []):
if attr.get('name') == 'support_level':
support_level = attr.get('value')
elif attr.get('name') == 'usage':
usage = attr.get('value')
valid_subs.append(ValidSub(sku, sub['productName'], support_level, end_date, trial, quantity, pool_id, satellite))
valid_subs.append(
ValidSub(sku, sub['productName'], support_level, end_date, trial, quantity, pool_id, satellite, subscription_id, account_number, usage)
)
if valid_subs:
licenses = []
@ -350,6 +371,7 @@ class Licenser(object):
license._attrs['instance_count'] = int(sub.quantity)
license._attrs['sku'] = sub.sku
license._attrs['support_level'] = sub.support_level
license._attrs['usage'] = sub.usage
license._attrs['license_type'] = 'enterprise'
if sub.trial:
license._attrs['trial'] = True
@ -364,6 +386,8 @@ class Licenser(object):
license._attrs['valid_key'] = True
license.update(license_date=int(sub.end_date.strftime('%s')))
license.update(pool_id=sub.pool_id)
license.update(subscription_id=sub.subscription_id)
license.update(account_number=sub.account_number)
licenses.append(license._attrs.copy())
return licenses

View File

@ -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)

View File

@ -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
# ----------------

View File

@ -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

View File

@ -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

View File

@ -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'):

View File

@ -28,7 +28,7 @@ import { getLanguageWithoutRegionCode } from 'util/language';
import Metrics from 'screens/Metrics';
import SubscriptionEdit from 'screens/Setting/Subscription/SubscriptionEdit';
import useTitle from 'hooks/useTitle';
import { dynamicActivate } from './i18nLoader';
import { dynamicActivate, locales } from './i18nLoader';
import getRouteConfig from './routeConfig';
import { SESSION_REDIRECT_URL } from './constants';
@ -142,9 +142,15 @@ function App() {
const searchParams = Object.fromEntries(new URLSearchParams(search));
const pseudolocalization =
searchParams.pseudolocalization === 'true' || false;
const language =
let language =
searchParams.lang || getLanguageWithoutRegionCode(navigator) || 'en';
if (!Object.keys(locales).includes(language)) {
// If there isn't a string catalog available for the browser's
// preferred language, default to one that has strings.
language = 'en';
}
useEffect(() => {
dynamicActivate(language, pseudolocalization);
}, [language, pseudolocalization]);

View File

@ -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}
/>
<InputField
name="LOCAL_PASSWORD_MIN_LENGTH"
config={authentication.LOCAL_PASSWORD_MIN_LENGTH}
type="number"
isRequired
/>
<InputField
name="LOCAL_PASSWORD_MIN_DIGITS"
config={authentication.LOCAL_PASSWORD_MIN_DIGITS}
type="number"
isRequired
/>
<InputField
name="LOCAL_PASSWORD_MIN_UPPER"
config={authentication.LOCAL_PASSWORD_MIN_UPPER}
type="number"
isRequired
/>
<InputField
name="LOCAL_PASSWORD_MIN_SPECIAL"
config={authentication.LOCAL_PASSWORD_MIN_SPECIAL}
type="number"
isRequired
/>
{submitError && <FormSubmitError error={submitError} />}
{revertError && <FormSubmitError error={revertError} />}
</FormColumnLayout>

View File

@ -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('<MiscAuthenticationEdit />', () => {

View File

@ -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 {<variable name>}.",
"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",

View File

@ -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":"<name>",
"teams":"<name>++<organization.name>",
"credential_types":"<name>+<kind>",
"credentials":"<name>++<credential_type.name>+<credential_type.kind>++<organization.name>",
"notification_templates":"<name>++<organization.name>",
"job_templates":"<name>++<organization.name>",
"projects":"<name>++<organization.name>",
"inventories":"<name>++<organization.name>",
"hosts":"<name>++<inventory.name>++<organization.name>",
"groups":"<name>++<inventory.name>++<organization.name>",
"inventory_sources":"<name>++<inventory.name>++<organization.name>",
"inventory_scripts":"<name>++<organization.name>",
"instance_groups":"<name>",
"labels":"<name>++<organization.name>",
"workflow_job_templates":"<name>++<organization.name>",
"workflow_job_template_nodes":"<identifier>++<workflow_job_template.name>++<organization.name>",
"applications":"<name>++<organization.name>",
"users":"<username>",
"instances":"<hostname>"
"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": "<name>",
"teams": "<name>++<organization.name>",
"credential_types": "<name>+<kind>",
"credentials": "<name>++<credential_type.name>+<credential_type.kind>++<organization.name>",
"notification_templates": "<name>++<organization.name>",
"job_templates": "<name>++<organization.name>",
"projects": "<name>++<organization.name>",
"inventories": "<name>++<organization.name>",
"hosts": "<name>++<inventory.name>++<organization.name>",
"groups": "<name>++<inventory.name>++<organization.name>",
"inventory_sources": "<name>++<inventory.name>++<organization.name>",
"inventory_scripts": "<name>++<organization.name>",
"instance_groups": "<name>",
"labels": "<name>++<organization.name>",
"workflow_job_templates": "<name>++<organization.name>",
"workflow_job_template_nodes": "<identifier>++<workflow_job_template.name>++<organization.name>",
"applications": "<name>++<organization.name>",
"users": "<username>",
"instances": "<hostname>"
},
"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,

View File

@ -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

View File

@ -159,7 +159,7 @@ def main():
# Here we are going to setup a dict of values to export
export_args = {}
for resource in EXPORTABLE_RESOURCES:
if module.params.get('all') or module.params.get(resource) == 'all':
if module.params.get('all') or module.params.get(resource) == ['all']:
# If we are exporting everything or we got the keyword "all" we pass in an empty string for this asset type
export_args[resource] = ''
else:

View File

@ -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:

View File

@ -337,6 +337,7 @@ EXAMPLES = '''
playbook: "ping.yml"
credentials:
- "Local"
- "2nd credential"
state: "present"
controller_config_file: "~/tower_cli.cfg"
survey_enabled: yes

View File

@ -461,7 +461,9 @@ EXAMPLES = '''
failure_nodes:
- identifier: node201
always_nodes: []
credentials: []
credentials:
- local_cred
- suplementary cred
- identifier: node201
unified_job_template:
organization:

View File

@ -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 = {}

View File

@ -76,3 +76,28 @@ Wait a few minutes for the periodic AWX task to do a health check against the ne
## Removing instances
You can remove an instance by clicking "Remove" in the Instances page, or by setting the instance `node_state` to "deprovisioning" via the API.
## Troubleshooting
### Fact cache not working
Make sure the system timezone on the execution node matches `settings.TIME_ZONE` (default is 'UTC') on AWX.
Fact caching relies on comparing modified times of artifact files, and these modified times are not timezone-aware. Therefore, it is critical that the timezones of the execution nodes match AWX's timezone setting.
To set the system timezone to UTC
`ln -s /usr/share/zoneinfo/Etc/UTC /etc/localtime`
### Permission denied errors
Jobs may fail with the following error
```
"msg":"exec container process `/usr/local/bin/entrypoint`: Permission denied"
```
or similar
For RHEL based machines, this could due to SELinux that is enabled on the system.
You can pass these `extra_settings` container options to override SELinux protections.
`DEFAULT_CONTAINER_RUN_OPTIONS = ['--network', 'slirp4netns:enable_ipv6=true', '--security-opt', 'label=disable']`

View File

@ -149,9 +149,8 @@ frozenlist==1.3.3
# via
# aiohttp
# aiosignal
# via
# -r /awx_devel/requirements/requirements_git.txt
# django-radius
future==0.18.3
# via django-radius
gitdb==4.0.10
# via gitpython
gitpython==3.1.30

View File

@ -2,6 +2,6 @@ git+https://github.com/ansible/system-certifi.git@devel#egg=certifi
# Remove pbr from requirements.in when moving ansible-runner to requirements.in
git+https://github.com/ansible/ansible-runner.git@devel#egg=ansible-runner
# django-radius has an aggressive pin of future==0.16.0, see https://github.com/robgolding/django-radius/pull/25
# There is a PR against django-radius that would fix this: https://github.com/robgolding/django-radius/pull/27
git+https://github.com/ansible/django-radius.git@develop#egg=django-radius
git+https://github.com/PythonCharmers/python-future@master#egg=future
git+https://github.com/ansible/python3-saml.git@devel#egg=python3-saml

View File

@ -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=<your username>
export CONTROLLER_PASSWORD=<your 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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
{
"TACACSPLUS_HOST": "tacacs",
"TACACSPLUS_PORT": 49,
"TACACSPLUS_SECRET": "ciscotacacskey",
"TACACSPLUS_SESSION_TIMEOUT": 5,
"TACACSPLUS_AUTH_PROTOCOL": "ascii"
}

View File

@ -101,7 +101,7 @@ stdout_events_enabled = true
stderr_events_enabled = true
[group:tower-processes]
programs=awx-dispatcher,awx-receiver,awx-uwsgi,awx-daphne,awx-nginx,awx-wsrelay,awx-rsyslogd,awx-heartbeet, awx-rsyslog-configurer
programs=awx-dispatcher,awx-receiver,awx-uwsgi,awx-daphne,awx-nginx,awx-wsrelay,awx-rsyslogd,awx-heartbeet,awx-rsyslog-configurer,awx-cache-clear
priority=5
[program:awx-autoreload]