Files
awx-operator/roles/installer/templates/configmaps/config.yaml.j2
Hao Liu e0ce3ef71d [AAP-64061] Add nginx log markers for direct API access detection (#2100)
Add map directives for X-Trusted-Proxy and X-DAB-JW-TOKEN headers to
log the presence of these headers as trusted_proxy_present and
dab_jwt_present fields in the nginx access log.

These markers enable the detection tool (aap-detect-direct-component-access)
to identify direct API access that bypasses AAP Gateway.

Also add explicit error_log /dev/stderr warn; instead of relying on
container base image symlinks.

Part of ANSTRAT-1840: Remove direct API access to platform components.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-17 17:25:36 -05:00

349 lines
13 KiB
Django/Jinja

# AWX Web ConfigMap.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: '{{ ansible_operator_meta.name }}-{{ deployment_type }}-configmap'
namespace: '{{ ansible_operator_meta.namespace }}'
labels:
{{ lookup("template", "../common/templates/labels/common.yaml.j2") | indent(width=4) | trim }}
data:
environment: |
AWX_SKIP_MIGRATIONS=true
settings: |
import os
import socket
# Import all so that extra_settings works properly
try:
# Some AWX setups may have no django_auth_ldap dependency.
from django_auth_ldap.config import *
except:
pass
def get_secret():
if os.path.exists("/etc/tower/SECRET_KEY"):
return open('/etc/tower/SECRET_KEY', 'rb').read().strip()
ADMINS = ()
STATIC_ROOT = '/var/lib/awx/public/static'
STATIC_URL = '{{ (ingress_path + '/static/').replace('//', '/') }}'
PROJECTS_ROOT = '/var/lib/awx/projects'
JOBOUTPUT_ROOT = '/var/lib/awx/job_status'
IS_K8S = True
{% if "limits" in task_resource_requirements and "memory" in task_resource_requirements["limits"] %}
# Set memory available based off of resource request/limit for the task pod
SYSTEM_TASK_ABS_MEM = '{{ task_resource_requirements["limits"]["memory"] }}'
{% endif %}
{% if "limits" in task_resource_requirements and "cpu" in task_resource_requirements["limits"] %}
# Set cpu available based off of resource request/limit for the task pod
SYSTEM_TASK_ABS_CPU = '{{ task_resource_requirements["limits"]["cpu"] }}'
{% endif %}
{%- set cpu_limit = task_resource_requirements["limits"]["cpu"] if "limits" in task_resource_requirements and "cpu" in task_resource_requirements["limits"] -%}
{%- if cpu_limit is defined -%}
{%- set callback_receiver_cpu = cpu_limit | cpu_string_to_decimal -%}
{%- if callback_receiver_cpu |int > 4 %}
# Set callback receiver workers based off cpu limit, default workers are 4, but if we have more than 4 cpu we can set higher value for workers
JOB_EVENT_WORKERS = {{ callback_receiver_cpu }}
{%- endif -%}
{%- endif %}
SECRET_KEY = get_secret()
ALLOWED_HOSTS = ['*']
INTERNAL_API_URL = 'http://127.0.0.1:8052'
{% if api_urlpattern_prefix | length > 0 %}
OPTIONAL_API_URLPATTERN_PREFIX = '{{ api_urlpattern_prefix }}'
{% endif %}
# Container environments don't like chroots
AWX_PROOT_ENABLED = False
# Automatically deprovision pods that go offline
AWX_AUTO_DEPROVISION_INSTANCES = True
CLUSTER_HOST_ID = socket.gethostname()
SYSTEM_UUID = os.environ.get('MY_POD_UID', '00000000-0000-0000-0000-000000000000')
CSRF_COOKIE_SECURE = {{ csrf_cookie_secure | bool }}
SESSION_COOKIE_SECURE = {{ session_cookie_secure | bool }}
SERVER_EMAIL = 'root@localhost'
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
EMAIL_SUBJECT_PREFIX = '[AWX] '
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
USE_X_FORWARDED_PORT = True
BROADCAST_WEBSOCKET_PORT = 8052
BROADCAST_WEBSOCKET_PROTOCOL = 'http'
RECEPTOR_LOG_LEVEL = '{{ receptor_log_level }}'
{% for item in extra_settings | default([]) %}
{{ item.setting }} = {{ item.value }}
{% endfor %}
nginx_conf: |
worker_processes {{ nginx_worker_processes }};
worker_cpu_affinity {{ nginx_worker_cpu_affinity }};
pid /tmp/nginx.pid;
events {
worker_connections {% if nginx_worker_connections > 1024 %}{{ nginx_worker_connections }}{% else %}1024{% endif %};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size {{ nginx_client_max_body_size }}M;
map $http_x_trusted_proxy $trusted_proxy_present {
default "trusted-proxy";
"" "-";
}
map $http_x_dab_jw_token $dab_jwt_present {
default "dab-jwt";
"" "-";
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$trusted_proxy_present $dab_jwt_present';
access_log /dev/stdout main;
error_log /dev/stderr warn;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
sendfile on;
#tcp_nopush on;
#gzip on;
upstream uwsgi {
server 127.0.0.1:8050;
}
upstream daphne {
server 127.0.0.1:8051;
}
{% if route_tls_termination_mechanism | lower == 'passthrough' %}
server {
listen 8052 default_server{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% if not ipv6_disabled %}
listen [::]:8052 default_server{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% endif %}
server_name _;
# Redirect all HTTP links to the matching HTTPS page
return 301 https://$host:8053$request_uri;
}
{% endif %}
server {
{% if route_tls_termination_mechanism | lower == 'passthrough' %}
listen 8053 ssl{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% if not ipv6_disabled %}
listen [::]:8053 ssl{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% endif %}
ssl_certificate /etc/nginx/pki/web.crt;
ssl_certificate_key /etc/nginx/pki/web.key;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
{% else %}
listen 8052 default_server{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% if not ipv6_disabled %}
listen [::]:8052 default_server{% if nginx_listen_queue_size > 511 %} backlog={{ nginx_listen_queue_size }}{% endif %};
{% endif %}
{% endif %}
# If you have a domain name, this is where to add it
server_name _;
keepalive_timeout 65;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
# Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
add_header X-Frame-Options "DENY";
# Protect against MIME content sniffing https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
add_header X-Content-Type-Options nosniff;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
location {{ (ingress_path + '/static').replace('//', '/') }} {
alias /var/lib/awx/public/static/;
}
location {{ (ingress_path + '/locales').replace('//', '/') }} {
alias /var/lib/awx/public/static/awx/locales;
}
location {{ (ingress_path + '/favicon.ico').replace('//', '/') }} {
alias /var/lib/awx/public/static/media/favicon.ico;
}
{% if api_urlpattern_prefix | length > 0 %}
location ~ ^({{ (ingress_path + '/websocket/').replace('//', '/') }}|{{ (ingress_path + '/api/websocket/').replace('//', '/') }}|{{ (ingress_path + '/api/' + api_urlpattern_prefix + '/v2/websocket/').replace('//', '/') }}) {
{% else %}
location ~ ^({{ (ingress_path + '/websocket/').replace('//', '/') }}|{{ (ingress_path + '/api/websocket/').replace('//', '/') }}) {
{% endif %}
# Pass request to the upstream alias
proxy_pass http://daphne;
# Require http version 1.1 to allow for upgrade requests
proxy_http_version 1.1;
# We want proxy_buffering off for proxying to websockets.
proxy_buffering off;
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if you use HTTPS:
proxy_set_header X-Forwarded-Proto https;
# pass the Host: header from the client for the sake of redirects
proxy_set_header Host $http_host;
# We've set the Host header, so we don't need Nginx to muddle
# about with redirects
proxy_redirect off;
# Depending on the request value, set the Upgrade and
# connection headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location {{ ingress_path }} {
# Add trailing / if missing
rewrite ^(.*)$http_host(.*[^/])$ $1$http_host$2/ permanent;
uwsgi_read_timeout {{ nginx_read_timeout }}s;
uwsgi_pass uwsgi;
include /etc/nginx/uwsgi_params;
include /etc/nginx/conf.d/*.conf;
proxy_set_header X-Forwarded-Port 443;
uwsgi_param HTTP_X_FORWARDED_PORT 443;
add_header Strict-Transport-Security max-age=15768000;
# Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options nosniff;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Expires "0";
add_header Pragma "no-cache";
# Return 503 Service Unavailable with JSON response if uWSGI fails to respond
error_page 504 =503 /json_503;
error_page 502 =503 /json_503; # Optional, in case uWSGI is completely down
}
location = /json_503 {
# Custom JSON response for 503 Service Unavailable
internal;
add_header Content-Type application/json;
# Check if X-Request-ID is set and include it in the response
if ($http_x_request_id) {
return 503 '{"status": "error", "message": "Service Unavailable", "code": 503, "request_id": "$http_x_request_id"}';
}
# If X-Request-ID is not set, just return the basic JSON response
return 503 '{"status": "error", "message": "Service Unavailable", "code": 503}';
}
}
}
redis_conf: |
unixsocket /var/run/redis/redis.sock
unixsocketperm 777
port 0
bind 127.0.0.1
timeout 300
receptor_conf: |
---
- log-level: {{ receptor_log_level }}
- local-only: null
- node:
firewallrules:
- action: reject
tonode: HOSTNAME
toservice: control
- control-service:
service: control
filename: /var/run/receptor/receptor.sock
permissions: '0660'
- work-command:
worktype: local
command: ansible-runner
params: worker
allowruntimeparams: true
- work-kubernetes:
worktype: kubernetes-runtime-auth
authmethod: runtime
allowruntimeauth: true
allowruntimepod: true
allowruntimeparams: true
- work-kubernetes:
worktype: kubernetes-incluster-auth
authmethod: incluster
allowruntimeauth: true
allowruntimepod: true
allowruntimeparams: true
- tls-client:
cert: /etc/receptor/tls/receptor.crt
key: /etc/receptor/tls/receptor.key
name: tlsclient
rootcas: /etc/receptor/tls/ca/mesh-CA.crt
mintls13: false
- work-signing:
privatekey: /etc/receptor/work_private_key.pem
tokenexpiration: 1m
uwsgi_conf: |
[uwsgi]
socket = 127.0.0.1:8050
processes = {{ uwsgi_processes }}
listen = {{ uwsgi_listen_queue_size }}
master = true
vacuum = true
no-orphans = true
lazy-apps = true
manage-script-name = true
master-fifo = /var/lib/awx/awxfifo
max-requests = 1000
buffer-size = 32768
harakiri = {{ uwsgi_timeout }}
harakiri-graceful-timeout = {{ uwsgi_timeout_grace_period }}
harakiri-graceful-signal = 6
py-call-osafterfork = true
if-env = UWSGI_MOUNT_PATH
mount = %(_)=awx.wsgi:application
endif =
if-not-env = UWSGI_MOUNT_PATH
mount = /=awx.wsgi:application
endif =