AWX currently sets no CORS policy. This means that all cross-origin requests from a browser are denied.
While secure (fail-closed), a user may want to build a frontend that talks to the API, and therefore conditionally allow some domain requests. This would require AWX to have a CORS policy that allows some user-configured domains.
This would be off by default.
This would be a welcome addition. However, it may require a REST API change, since OPTIONS is being used in the API and OPTIONS verb is also sent in CORS preflight to check if Origin is from an accepted domain.
In basic experiments to allow for CORS, I modified the nginx.conf location / to allow wide-open CORS.
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization';
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
This works, however, it also cripples the REST API (use of OPTIONS) and broke tower-cli. For example, tower-cli depends on OPTIONS to add projects (uses options to get organization info).
See: https://github.com/ansible/tower-cli/blob/master/tower_cli/resources/project.py#L116
r = client.options('/projects/')
The return code in the nginx.conf, any return code, in OPTIONS is what makes CORS works but cripples the AWX rest api since it depends on OPTIONS.
@wenottingham I looked at awx code and it is using DRF, so what about using django-cors-headers package recommended by DRF
If we agree to use this package I can work on this issue and implement the change.
@EmadMokhtar, @wenottingham - The above PR has changes that made CORS requests work for me. I haven't done Django development in a while and would appreciate review. I wanted to add django-cors-headers to the project, but still require users to manually enable CORS in /etc/{awx,tower}/settings.py.
We've just released a new version of awx (https://github.com/ansible/awx/releases/tag/3.0.0) which adds support for the Django CORS middleware.
Thanks guys. Works perfectly now
Beer for you guys!
Best Regards from Warsaw
Is there any documentation available for configuring the new middleware? I attempted adding CORS_ORIGIN_ALLOW_ALL = True to /etc/tower/settings.py and restarted the AWX_WEB container but that doesn't seem to have had any effect. Running AWX 3.0.1.0
I am also trying to configure CORS in the settings.py. I installed django-cors-headers and added in setup.py :
INSTALLED_APPS = ['corsheaders' ]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware'
]
CORS_ORIGIN_ALLOW_ALL = True
I am getting all kinds of errors! Running AWX 3.3.7. Any help would be much appreciated from those who have configured this already.
@themalaikulfi
3.3.7 isn't an AWX version number (https://github.com/ansible/awx/releases), though it is a Red Hat Ansible Tower version number.
I'll note that the CORS functionality you're referring to is likely not _present_ in that version of Ansible Tower (these release notes suggest it was added in Tower 3.5.0: https://docs.ansible.com/ansible-tower/latest/html/release-notes/relnotes.html#ansible-tower-version-3-5-0)
If you're looking for help troubleshooting an Ansible Tower installation, please reach out to Red Hat directly.
Hi @ryanpetrello ,
Sorry- It is AWX 6.1.0 which I believe matches Tower 3.3.7
From this thread, I had assumed that Tower 3.0.0+ would have CORS functionality so I am surprised that it is actually in 3.5.0. Would CORS would be present in the 6.1.0 AWX version? And if not, which version should we upgrade to in order to have that feature?
Thank you
@themalaikulfi,
I'm still a little unclear on what you're running (AWX vs Tower). If it's Tower, you'll have to reach out to Red Hat for confirmation on what versions of Ansible Tower support which features; it is a downstream product built on AWX, and the AWX community and contributors don't provide support for it here.
If you're running AWX, the CORS functionality should be present in AWX versions higher than 3.0.0 (as evidenced by a few people in this thread who've successfully used it: https://github.com/ansible/awx/issues/1519#issuecomment-458516632)
In terms of enabling it, follow the instructions here:
https://github.com/adamchainz/django-cors-headers#setup
I haven't tested this myself, but based on those instructions, you'll probably want to do something _like_ this at the bottom of /etc/tower/settings.py:
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware'] + MIDDLEWARE
CORS_ORIGIN_ALLOW_ALL = True
Hi @ryanpetrello
We are running AWX 6.1.0.
Hmm, yes so I did try all those steps -- installing django-cors-headers package and updating the setup.py with those variables and INSTALLED_APPS. Maybe the problem was we added those things at the top of setup.py? Because we got lots of errors when we tried that (errors below):
This showed up constantly:
Type 'awx-manage help' for usage.
2019-08-21 18:06:17,889 INFO spawned: 'channels-worker' with pid 7188
2019-08-21 18:06:17,889 INFO success: callback-receiver entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2019-08-21 18:06:18,052 INFO success: dispatcher entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2019-08-21 18:06:18,052 INFO exited: callback-receiver (exit status 1; not expected)
Unknown command: 'run_dispatcher'
Type 'awx-manage help' for usage.
2019-08-21 18:06:18,401 INFO spawned: 'callback-receiver' with pid 7190
2019-08-21 18:06:18,543 INFO exited: dispatcher (exit status 1; not expected)
Unknown command: 'runworker'
And this was the other error:
127.0.0.1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"elapsed": 0,
"match_groupdict": {},
"match_groups": [],
"path": null,
"port": 5672,
"search_regex": null,
"state": "started"
}
Using /etc/ansible/ansible.cfg as config file
127.0.0.1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"db": "awx"
}
Traceback (most recent call last):
File "/usr/bin/awx-manage", line 11, in
load_entry_point('awx==6.1.0.0', 'console_scripts', 'awx-manage')()
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/__init__.py", line 142, in manage
execute_from_command_line(sys.argv)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(args, cmd_options)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 361, in execute
self.check()
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 390, in check
include_deployment_checks=include_deployment_checks,
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/commands/migrate.py", line 65, in _run_checks
issues.extend(super()._run_checks(kwargs))
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 377, in _run_checks
return checks.run_checks(kwargs)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/checks/registry.py", line 72, in run_checks
new_errors = check(app_configs=app_configs)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/checks/urls.py", line 40, in check_url_namespaces_unique
all_namespaces = _load_all_namespaces(resolver)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/checks/urls.py", line 57, in _load_all_namespaces
url_patterns = getattr(resolver, 'url_patterns', [])
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/urls/resolvers.py", line 579, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/urls/resolvers.py", line 572, in urlconf_module
return import_module(self.urlconf_name)
File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "
File "
File "
File "
File "
File "
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/urls.py", line 6, in
from awx.main.views import (
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/main/views.py", line 14, in
from rest_framework import exceptions, permissions, views
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/views.py", line 19, in
from rest_framework.schemas import DefaultSchema
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/schemas/__init__.py", line 33, in
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/settings.py", line 227, in __getattr__
val = perform_import(val, attr)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/settings.py", line 172, in perform_import
return [import_from_string(item, setting_name) for item in val]
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/settings.py", line 172, in
return [import_from_string(item, setting_name) for item in val]
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/rest_framework/settings.py", line 183, in import_from_string
module = import_module(module_path)
File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/api/permissions.py", line 12, in
from awx.main.access import check_user_access
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/main/access.py", line 13, in
from django.contrib.auth.models import User
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/contrib/auth/models.py", line 3, in
from django.contrib.contenttypes.models import ContentType
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/contrib/contenttypes/models.py", line 133, in
class ContentType(models.Model):
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/db/models/base.py", line 111, in __new__
"INSTALLED_APPS." % (module, name)
RuntimeError: Model class django.contrib.contenttypes.models.ContentType doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
Traceback (most recent call last):
File "/usr/bin/awx-manage", line 11, in
load_entry_point('awx==6.1.0.0', 'console_scripts', 'awx-manage')()
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/awx/__init__.py", line 142, in manage
execute_from_command_line(sys.argv)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/base.py", line 364, in execute
output = self.handle(args, **options)
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/core/management/commands/shell.py", line 92, in handle
exec(sys.stdin.read())
File "
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/contrib/auth/models.py", line 3, in
from django.contrib.contenttypes.models import ContentType
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/contrib/contenttypes/models.py", line 133, in
class ContentType(models.Model):
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/db/models/base.py", line 111, in __new__
"INSTALLED_APPS." % (module, name)
RuntimeError: Model class django.contrib.contenttypes.models.ContentType doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
Unknown command: 'create_preload_data'
Type 'awx-manage help' for usage.
@themalaikulfi I'm not sure what's going on with your install, but I've applied just these settings in a 6.1.0 I just spun up:
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware'] + MIDDLEWARE
CORS_ORIGIN_ALLOW_ALL = True
Note that I did not have to add corsheaders to INSTALLED_APPS, because AWX already does that for you:
~ curl -skI -X OPTIONS "https://awx.example.org:8043/api/" -H "Origin: https://example.org" | grep Access-
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: accept, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with
Access-Control-Allow-Methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
Access-Control-Max-Age: 86400
...and it works fine for me.
@ryanpetrello
Ah! That is very helpful! Updated the setup.py and restarted AWX and now seeing this error. I think this is related to migration and is not caused by CORS.
Using /etc/ansible/ansible.cfg as config file
127.0.0.1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"db": "awx"
}
Operations to perform:
Apply all migrations: auth, conf, contenttypes, main, oauth2_provider, sessions, sites, social_django, sso, taggit
Running migrations:
No migrations to apply.
Traceback (most recent call last):
File "/var/lib/awx/venv/awx/lib64/python3.6/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
psycopg2.IntegrityError: duplicate key value violates unique constraint "auth_user_username_key"
DETAIL: Key (username)=(admin) already exists.
@ryanpetrello
Update: I have spun up my own fresh containers with AWX installer using docker-compose, updated the awx_web container settings.py with those 2 lines, and now I am able to access the API. Thank you for the troubleshooting!
Most helpful comment
@ryanpetrello
Update: I have spun up my own fresh containers with AWX installer using docker-compose, updated the awx_web container settings.py with those 2 lines, and now I am able to access the API. Thank you for the troubleshooting!