Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic API_HOSTNAME & CONTENT_ORIGIN via dynaconf+django-crum #2134

Merged
merged 10 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions galaxy_ng/app/dynaconf_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from dynaconf import Dynaconf, Validator
from galaxy_ng.app.dynamic_settings import DYNAMIC_SETTINGS_SCHEMA
from django.apps import apps
from crum import get_current_request


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -645,7 +646,10 @@ def configure_dynamic_settings(settings: Dynaconf) -> Dict[str, Any]:
change the value before it is returned allowing reading overrides from
database and cache.
"""
if settings.get("GALAXY_DYNAMIC_SETTINGS") is not True:
# we expect a list of function names here, which have to be in scope of
# locals() for this specific file
enabled_hooks = settings.get("DYNACONF_AFTER_GET_HOOKS")
if not enabled_hooks:
return {}

# Perform lazy imports here to avoid breaking when system runs with older
Expand Down Expand Up @@ -710,8 +714,43 @@ def read_settings_from_cache_or_db(

return temp_settings.get(key, value.value)

def alter_hostname_settings(
temp_settings: Settings,
value: HookValue,
key: str,
*args,
**kwargs
) -> Any:
"""Use the request headers to dynamically alter the content origin and api hostname.
This is useful in scenarios where the hub is accessible directly and through a
reverse proxy.
"""

# we only want to modify these settings base on request headers
ALLOWED_KEYS = ['CONTENT_ORIGIN', 'ANSIBLE_API_HOSTNAME']

# If app is starting up or key is not on allowed list bypass and just return the value
if not apps.ready or key.upper() not in ALLOWED_KEYS:
return value.value

# we have to assume the proxy or the edge device(s) set these headers correctly
req = get_current_request()
if req is not None:
headers = dict(req.headers)
proto = headers.get("X-Forwarded-Proto", "http")
host = headers.get("Host", "localhost:5001")
baseurl = proto + "://" + host
return baseurl

return value.value

# avoid scope errors by not using a list comprehension
hook_functions = []
for func_name in enabled_hooks:
hook_functions.append(Hook(locals()[func_name]))

return {
"_registered_hooks": {
Action.AFTER_GET: [Hook(read_settings_from_cache_or_db)]
Action.AFTER_GET: hook_functions
}
}
2 changes: 1 addition & 1 deletion galaxy_ng/app/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):

def show_form_for_method(self, view, method, request, obj):
"""Display forms only for superuser."""
if request.user.is_superuser:
if request.user and request.user.is_superuser:
return super().show_form_for_method(view, method, request, obj)
return False
2 changes: 2 additions & 0 deletions galaxy_ng/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
# END: Pulp standard middleware
'django_prometheus.middleware.PrometheusAfterMiddleware',
]
MIDDLEWARE += ('crum.CurrentRequestUserMiddleware',)

INSTALLED_APPS = [
'rest_framework.authtoken',
'crum',
'dynaconf_merge',
]

Expand Down
2 changes: 1 addition & 1 deletion galaxy_ng/app/webserver_snippets/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ location /ui/ {

location /api/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
Expand Down
15 changes: 15 additions & 0 deletions galaxy_ng/tests/integration/dab/test_url_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,18 @@ def test_dab_collection_download_url_hostnames(settings, galaxy_client, publishe
# make sure the final redirect was through the gateway ...
expected_url = gc.galaxy_root.replace('/api/galaxy/', '')
assert dl_resp.url.startswith(expected_url)

# now check if we access it from localhost that the download url changes accordingly
if gc.galaxy_root == "http://jwtproxy:8080/api/galaxy/":
local_url = os.path.join(gc.galaxy_root, cv_url)
local_url = local_url.replace("http://jwtproxy:8080", "http://localhost:5001")
cv_info = gc.get(local_url, auth=("admin", "admin"))

download_url = cv_info["download_url"]
assert download_url.startswith("http://localhost:5001")

# try to GET the tarball ...
dl_resp = gc.get(download_url, parse_json=False, auth=("admin", "admin"))
assert dl_resp.status_code == 200
assert dl_resp.headers.get('Content-Type') == 'application/gzip'
assert dl_resp.url.startswith("http://localhost:5001")
9 changes: 8 additions & 1 deletion profiles/dab/pulp_config.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ PULP_GALAXY_FEATURE_FLAGS__external_authentication=true

PULP_TOKEN_SERVER="https://localhost/token/"

# ease-of-use
ENABLE_SIGNING=1
PULP_GALAXY_AUTO_SIGN_COLLECTIONS=true
PULP_GALAXY_REQUIRE_CONTENT_APPROVAL=true
PULP_GALAXY_COLLECTION_SIGNING_SERVICE=ansible-default
PULP_GALAXY_CONTAINER_SIGNING_SERVICE=container-default

# dynamic download urls
PULP_GALAXY_DYNAMIC_SETTINGS=true
PULP_DYNACONF_AFTER_GET_HOOKS=["read_settings_from_cache_or_db", "alter_hostname_settings"]
2 changes: 1 addition & 1 deletion profiles/dab_jwt/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ func main() {
log.Printf("Request: %s %s", req.Method, req.URL.String())

// just assume this proxy is http ...
req.Header.Add("X-Forwarded-Proto", "https")
req.Header.Add("X-Forwarded-Proto", "http")

// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-envoy-internal
req.Header.Add("X-Envoy-Internal", "true")
Expand Down
2 changes: 1 addition & 1 deletion profiles/dab_jwt/pulp_config.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ PULP_GALAXY_COLLECTION_SIGNING_SERVICE=ansible-default
PULP_GALAXY_CONTAINER_SIGNING_SERVICE=container-default

# dynamic download urls
PULP_GALAXY_DYNAMIC_SETTINGS=true
PULP_DYNACONF_AFTER_GET_HOOKS=["read_settings_from_cache_or_db", "alter_hostname_settings"]
4 changes: 3 additions & 1 deletion requirements/requirements.common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ django-ansible-base[jwt_consumer] @ git+https://github.com/ansible/django-ansibl
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
django-crum==0.7.9
# via django-ansible-base
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-filter==23.5
# via pulpcore
django-guid==3.4.0
Expand Down
4 changes: 3 additions & 1 deletion requirements/requirements.insights.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ django-ansible-base[jwt_consumer] @ git+https://github.com/ansible/django-ansibl
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
django-crum==0.7.9
# via django-ansible-base
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-filter==23.5
# via pulpcore
django-guid==3.4.0
Expand Down
4 changes: 3 additions & 1 deletion requirements/requirements.standalone.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ django-ansible-base[jwt_consumer] @ git+https://github.com/ansible/django-ansibl
django-auth-ldap==4.0.0
# via galaxy-ng (setup.py)
django-crum==0.7.9
# via django-ansible-base
# via
# django-ansible-base
# galaxy-ng (setup.py)
django-filter==23.5
# via pulpcore
django-guid==3.4.0
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def _format_pulp_requirement(plugin, specifier=None, ref=None, gh_namespace="pul
"boto3",
"distro",
"django-ansible-base[jwt_consumer] @ git+https://github.com/ansible/django-ansible-base@devel", # noqa 501
"django-crum==0.7.9",
# From vendored automated_logging
"marshmallow<4.0.0,>=3.6.1",
"django-picklefield<4.0.0,>=3.0.1",
Expand Down
Loading