Add support for CORS in HAProxy stats dashboard
- Introduced `HAPROXY_STATS_CORS_ORIGIN` environment variable for enabling CORS in HAProxy stats. - Updated HAProxy configuration template to handle CORS preflight and response headers. - Added tests for static configuration mode with CORS enabled. - Updated documentation to include new CORS configuration details and examples.
This commit is contained in:
parent
353fd392f6
commit
491f4a8c09
8 changed files with 203 additions and 23 deletions
|
|
@ -39,6 +39,7 @@ class ContainerEnv:
|
|||
"username": os.getenv("HAPROXY_USERNAME") if os.getenv("HAPROXY_USERNAME") else "admin",
|
||||
"password": os.getenv("HAPROXY_PASSWORD"),
|
||||
"port": os.getenv("HAPROXY_STATS_PORT") if os.getenv("HAPROXY_STATS_PORT") else "1936",
|
||||
"cors_origin": os.getenv("HAPROXY_STATS_CORS_ORIGIN", ""),
|
||||
}
|
||||
|
||||
env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv(
|
||||
|
|
@ -149,6 +150,8 @@ class ContainerEnv:
|
|||
os.environ['HAPROXY_PASSWORD'] = str(stats['password'])
|
||||
if 'port' in stats:
|
||||
os.environ['HAPROXY_STATS_PORT'] = str(stats['port'])
|
||||
if 'cors_origin' in stats:
|
||||
os.environ['HAPROXY_STATS_CORS_ORIGIN'] = str(stats['cors_origin'])
|
||||
|
||||
# Convert logLevel
|
||||
if 'logLevel' in yaml_config:
|
||||
|
|
@ -333,10 +336,12 @@ class DaemonizeHAProxy:
|
|||
self.custom_config_folder = custom_config_folder if custom_config_folder is not None else Consts.custom_config_folder
|
||||
|
||||
def haproxy(self, action):
|
||||
self.__prepare(self.get_haproxy_command(action))
|
||||
error = self.__prepare(self.get_haproxy_command(action), action)
|
||||
|
||||
if self.process is None:
|
||||
return
|
||||
if error or self.process is None:
|
||||
logger_haproxy.fatal(f"Failed to start HAProxy ({action}). Exiting.")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
self.thread = Process(target=self.__start, args=())
|
||||
self.thread.start()
|
||||
|
|
@ -360,10 +365,42 @@ class DaemonizeHAProxy:
|
|||
)
|
||||
return self.get_haproxy_command(DaemonizeHAProxy.HAPROXY_START, pid_file)
|
||||
|
||||
def __prepare(self, command):
|
||||
def __validate_config(self):
|
||||
"""Validate HAProxy configuration before starting."""
|
||||
validation_cmd = ["haproxy", "-c", "-f", Consts.haproxy_config]
|
||||
|
||||
# Add custom config files if they exist
|
||||
for config_file in self.get_custom_config_files().keys():
|
||||
validation_cmd.extend(["-f", config_file])
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
validation_cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return result.stderr if result.stderr else result.stdout
|
||||
return None
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return "HAProxy configuration validation timed out"
|
||||
except Exception as e:
|
||||
return f"Error validating configuration: {e}"
|
||||
|
||||
def __prepare(self, command, action=None):
|
||||
if not isinstance(command, (list, tuple)):
|
||||
command = shlex.split(command)
|
||||
|
||||
# Validate HAProxy config before starting (but not on reload - HAProxy validates itself during reload)
|
||||
if action == DaemonizeHAProxy.HAPROXY_START:
|
||||
validation_error = self.__validate_config()
|
||||
if validation_error:
|
||||
logger_haproxy.fatal(f"HAProxy configuration validation failed:\n{validation_error}")
|
||||
return validation_error
|
||||
|
||||
try:
|
||||
logger_haproxy.debug(f"HAPROXY command: {command}")
|
||||
self.process = subprocess.Popen(command,
|
||||
|
|
@ -372,9 +409,12 @@ class DaemonizeHAProxy:
|
|||
stderr=subprocess.PIPE,
|
||||
bufsize=-1,
|
||||
universal_newlines=True)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger_haproxy.error(f"{e}")
|
||||
error_msg = f"Failed to start HAProxy process: {e}"
|
||||
logger_haproxy.error(error_msg)
|
||||
return error_msg
|
||||
|
||||
def __start(self):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,23 @@ frontend stats
|
|||
bind *:{{ data_stats["port"] | default(1936) }}
|
||||
mode http
|
||||
http-request use-service prometheus-exporter if { path /metrics }
|
||||
{%- if data_stats["cors_origin"] | default("") != "" %}
|
||||
|
||||
# CORS for stats dashboard (only for configured origin)
|
||||
acl from_ui hdr(Origin) -i {{ data_stats["cors_origin"] }}
|
||||
acl preflight method OPTIONS
|
||||
|
||||
# Preflight response
|
||||
http-request return status 204 hdr "Access-Control-Allow-Origin" "%[req.hdr(Origin)]" hdr "Access-Control-Allow-Methods" "GET, OPTIONS" hdr "Access-Control-Allow-Headers" "Authorization, Content-Type" hdr "Access-Control-Max-Age" "86400" hdr "Vary" "Origin" if from_ui preflight
|
||||
|
||||
# Actual response headers
|
||||
http-after-response set-header Access-Control-Allow-Origin "{{ data_stats["cors_origin"] }}"
|
||||
http-after-response set-header Access-Control-Allow-Methods "GET, OPTIONS"
|
||||
http-after-response set-header Access-Control-Allow-Headers "Authorization, Content-Type"
|
||||
http-after-response set-header Access-Control-Expose-Headers "X-Request-ID"
|
||||
http-after-response set-header Vary "Origin"
|
||||
{% endif %}
|
||||
|
||||
stats enable
|
||||
stats hide-version
|
||||
stats realm Haproxy\ Statistics
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue