global
stats socket /var/run/haproxy/haproxy.sock mode 660 level admin expose-fd listeners
maxconn "$LUNAR_MAXCONN"
lua-prepend-path /etc/haproxy/lua/json.lua
lua-prepend-path /etc/haproxy/lua/http.lua
lua-load /etc/haproxy/lua/lunar.lua
pidfile /var/run/haproxy/haproxy.pid
tune.bufsize "$GATEWAY_BUFFER_SIZE"
defaults
log global
log 127.0.0.1:5141 len 3072 local0 info
retries 0
timeout connect "$LUNAR_CONNECT_TIMEOUT_SEC"s
timeout client "$LUNAR_CLIENT_TIMEOUT_SEC"s
timeout server "$LUNAR_SERVER_TIMEOUT_SEC"s
log-format '{ "internal": true, "timestamp":%Ts%ms, "duration":%Tr, "total_duration":%Ta, "method":"%HM", "url":"%[dst]%HU", "request_headers":"%hr", "response_headers":"%hs", "status_code":%ST }'
listen stats # Define a listen section called "stats"
bind :9000 # Listen on localhost:9000
mode http
stats enable # Enable stats page
stats hide-version # Hide HAProxy version
stats realm Haproxy\ Statistics # Title text for popup window
stats uri /metrics # Stats URI
listen healthcheck
bind *:${LUNAR_HEALTHCHECK_PORT}
mode http
log-format '{ "internal": true, "timestamp":%Ts%ms, "duration":%Tr, "total_duration":%Ta, "method":"%HM", "uri":"%HU", "url":"%[dst]%HU", "request_headers":"%hr", "response_headers":"%hs", "path":"%HP", "status_code":%ST }'
acl path_healthcheck path /healthcheck
http-request deny status 404 unless path_healthcheck
http-request return status 200 content-type text/plain lf-string "proxy is up"
frontend http-async-in
mode http
bind *:${ASYNC_SERVICE_BIND_PORT}
# _ # Lunar_Async_HTTPS_Binder # _ #
option h1-case-adjust-bogus-client
log-format '{ "internal": true, "timestamp":%Ts%ms, "duration":%Tr, "total_duration":%Ta, "method":"%HM", "uri":"%HU", "url":"%[dst]%HU", "request_headers":"%hr", "response_headers":"%hs", "path":"%HP", "status_code":%ST }'
acl skip_all var(proc.skip_all) -m found
acl proxy_shutting_down var(proc.disable_requests) -m found
acl path_is_retrieve path /retrieve
acl has_query_params urlp(sequence_id) -m found
acl is_retrieve_hdr_found req.hdr(x-lunar-async-retrieve) -m found
http-request deny status 503 content-type text/plain lf-string "Lunar Gateway is shuting down" hdr x-lunar-error 10 if proxy_shutting_down
http-request set-var(txn.lunar_request_id) uuid()
unique-id-format %[var(txn.lunar_request_id)]
http-request set-header x-lunar-sequence-id %[var(txn.lunar_request_id)] if !skip_all
http-response set-header x-lunar-sequence-id %[var(txn.lunar_request_id)] if !skip_all
default_backend async_service
frontend http-in
mode http
bind *:${BIND_PORT}
# _ # Lunar_HTTPS_Binder # _ #
option http-buffer-request # Buffer the request to allow for SPOE processing
option h1-case-adjust-bogus-client
acl skip_all var(proc.skip_all) -m found
acl proxy_shutting_down var(proc.disable_requests) -m found
log-format '{ "termination_state": "%ts","internal": %[var(txn.is_internal)], "request_id": "%[var(txn.lunar_request_id)]", "timestamp":%Ts%ms, "duration":%Tr, "total_duration":%Ta, "method":"%HM", "url":"%[var(txn.url)]", "host":"%[var(txn.host)]", "path":"%HP", "status_code":%ST, "request_active_remedies":%[var(txn.lunar.request_active_remedies)], "response_active_remedies":%[var(txn.lunar.response_active_remedies)], "interceptor":"%[var(txn.interceptor)]", "consumer_tag":"%[var(txn.lunar_consumer_tag)]", "x_lunar_error": "%[var(txn.x_lunar_error)]", "error_in_body": "%[var(txn.error_in_body)]" }'
# Define an ACL to check for the x-lunar-internal header
acl is_internal req.hdr(x-lunar-internal) -m str true
# Set txn.is_internal to "true" if the header is present, "false" otherwise
http-request set-var(txn.is_internal) str("true") if is_internal
http-request set-var(txn.is_internal) str("false") unless is_internal
http-request deny status 503 content-type text/plain lf-string "Lunar Gateway is shuting down" hdr x-lunar-error 10 if proxy_shutting_down
http-request set-var(txn.lunar_request_id) req.hdr(x-lunar-req-id) if { req.hdr(x-lunar-req-id) -m found }
http-request set-var(txn.lunar_request_id) uuid() unless { req.hdr(x-lunar-req-id) -m found }
unique-id-format %[var(txn.lunar_request_id)]
http-request set-var(txn.lunar_sequence_id) req.hdr(x-lunar-sequence-id) if { req.hdr(x-lunar-sequence-id) -m found }
http-request set-var(txn.lunar_sequence_id) unique-id unless { req.hdr(x-lunar-sequence-id) -m found }
http-response set-header x-lunar-sequence-id %[var(txn.lunar_sequence_id)] if !skip_all
http-request set-var(txn.lunar.method) method
http-request set-var(txn.lunar.request_active_remedies) str({}) unless { var(txn.lunar.request_active_remedies) -m found }
http-request set-var(txn.lunar.response_active_remedies) str({}) unless { var(txn.lunar.response_active_remedies) -m found }
# On Proxy generated error we want to add the x-lunar-error header to notify the Interceptor.
# Reference: https://docs.haproxy.org/2.6/configuration.html#4-http-error
http-error status 503 content-type text/plain string "The endpoint cannot be reached" hdr x-lunar-error 2
http-error status 504 content-type text/plain string "Gateway timeout" hdr x-lunar-error 3
http-error status 404 content-type text/plain string "Endpoint not found" hdr x-lunar-error 4
acl redirection_by_query_params env(LUNAR_REDIRECTION_BY_QUERY_PARAMS) -m str 1
# extract routing info from query params if needed and remove them from request
http-request set-var(txn.host) urlp(lunar_original_host) if redirection_by_query_params
http-request set-var(txn.scheme) urlp(lunar_original_scheme) if redirection_by_query_params
http-request set-var(txn.lunar_consumer_tag) urlp(lunar_consumer_tag) if redirection_by_query_params
http-request set-header Host %[var(txn.host)] if redirection_by_query_params
http-request set-query %[query,regsub(&?lunar_original_host=[^&]*,)] if redirection_by_query_params
http-request set-query %[query,regsub(&?lunar_original_scheme=[^&]*,)] if redirection_by_query_params
http-request set-query %[query,regsub(&?lunar_consumer_tag=[^&]*,)] if redirection_by_query_params
acl host-found var(txn.host) -m found
http-request set-var(txn.x_lunar_error) str(1) if !host-found redirection_by_query_params
http-request set-var(txn.error_in_body) str("Could not locate query params lunar_original_host and lunar_original_scheme") if !host-found redirection_by_query_params
http-request deny status 503 content-type text/plain lf-string "Could not locate query params lunar_original_host and lunar_original_scheme" hdr x-lunar-error 1 if !host-found redirection_by_query_params
# extract routing info from headers if needed and remove lunar-specific ones from request
acl x-lunar-host-found req.hdr(x-lunar-host) -m found
# deny request if x-lunar-host header is not found and redirection_by_query_params is not enabled
http-request set-var(txn.x_lunar_error) str(1) if !x-lunar-host-found !redirection_by_query_params
http-request set-var(txn.error_in_body) str("Could not locate header x-lunar-host") if !x-lunar-host-found !redirection_by_query_params
http-request deny status 401 content-type text/plain lf-string "Could not locate header x-lunar-host" hdr x-lunar-error 1 if !x-lunar-host-found !redirection_by_query_params
http-request set-var(txn.host) req.hdr(x-lunar-host) if !redirection_by_query_params x-lunar-host-found
http-request set-var(txn.host) req.hdr(Host) if !redirection_by_query_params !x-lunar-host-found
http-request set-header Host %[var(txn.host)] if !redirection_by_query_params x-lunar-host-found
http-request set-var(txn.scheme) req.hdr(x-lunar-scheme) unless redirection_by_query_params
http-request del-header x-lunar-scheme
http-request set-var(txn.lunar_consumer_tag) hdr(x-lunar-consumer-tag) unless redirection_by_query_params
# txn.url is set to the full URL excluding scheme, port and query params (e.g. domain.com/path/to/resource)
http-request set-var(txn.path) path
http-request set-var(txn.url) var(txn.host),concat(,txn.path)
# txn.lunar_interceptor
http-request set-var(txn.interceptor) str("lunar-direct/0") unless { req.hdr(x-lunar-interceptor) -m found }
http-request set-var(txn.interceptor) req.hdr(x-lunar-interceptor) if { req.hdr(x-lunar-interceptor) -m found }
http-request del-header x-lunar-interceptor if { req.hdr(x-lunar-interceptor) -m found }
# Block the request if it is not in allowed domains. By default all domains are allowed (.*).
acl allowed_domain var(txn.host) -m reg -f /etc/haproxy/allowed_domains.lst
http-request set-var(txn.x_lunar_error) str(6) if !allowed_domain
http-request set-var(txn.error_in_body) str("Host is not in allow list") if !allowed_domain
http-request deny if !allowed_domain
# Block the request if it is in blocked domains
acl blocked_domain var(txn.host) -m reg -f /etc/haproxy/blocked_domains.lst
http-request set-var(txn.x_lunar_error) str(7) if blocked_domain
http-request set-var(txn.error_in_body) str("Host is in block list") if blocked_domain
http-request deny if blocked_domain
# if no port in host string, it will return 0. (https://bit.ly/3ly3kGw)
http-request set-var(txn.dst_port) var(txn.host),port_only
acl dst_port_not_found var(txn.dst_port) -m int 0
acl is_https_scheme var(txn.scheme) -m str https
acl use_mtls var(txn.host),lower,map_reg(/etc/haproxy/maps/mtls.map) -m found
http-request set-var(txn.dst_port) int(443) if dst_port_not_found is_https_scheme
http-request set-var(txn.dst_port) int(80) if dst_port_not_found !is_https_scheme
http-request set-var(txn.x_lunar_error) str(5) if { var(txn.dst_port) -m int 0 }
http-request set-var(txn.error_in_body) str("Could not resolve port") if { var(txn.dst_port) -m int 0 }
http-request deny status 503 content-type text/plain lf-string "Could not resolve port" hdr x-lunar-error 5 if { var(txn.dst_port) -m int 0 }
# Check if the host is an IPv4 address and resolve it if it is not
acl is_host_ipv4 var(txn.host),host_only -i -m reg (\d+)\.(\d+)\.(\d+)\.(\d+)
# Lunar SPOE
acl is_managed var(proc.manage_all) -m found
acl is_managed capture.req.method,concat(":::",txn.url),map_reg(/etc/haproxy/maps/endpoints.map) -m found
acl body_required var(proc.body_from_all) -m found
acl body_required capture.req.method,concat(":::",txn.url),map_reg(/etc/haproxy/maps/spoe_with_body.map) -m found
acl is_res_error res.hdr(x-lunar-error) -m found
acl is_early_response var(txn.lunar.return_early_response) -m bool
# Capture request payload for requests that could be retried
acl capture_required capture.req.method,concat(":::",txn.url),map_reg(/etc/haproxy/maps/req_capture.map) -m found
acl capture_required var(proc.capture_all) -m found
# <len> is the maximum number of characters to extract from the value and
# report in the logs. The string will be truncated on the right if
# it exceeds.
declare capture request len 8000000 if capture_required is_managed
http-request capture req.body id 0 if capture_required is_managed body_required
# Store that captured body in txn.lunar.request_body
http-request set-var(txn.lunar.request_body) capture.req.hdr(0) if { capture.req.hdr(0) -m found } !skip_all capture_required is_managed body_required
http-request set-var(txn.lunar.request_headers_str) req.hdrs if !skip_all capture_required is_managed
http-request set-var(txn.lunar.query_params) query if !skip_all capture_required is_managed
filter spoe engine lunar config "${LUNAR_SPOE_CONFIG}"
http-request send-spoe-group lunar lunar-request-group if !skip_all !body_required is_managed
http-request send-spoe-group lunar lunar-full-request-group if !skip_all is_managed body_required
# Modify request (apply modifications and send back to proxy - localhost:8000)
acl is_looped_req req.hdr(x-lunar-lua-handled) -m found
http-request set-var(req.is_looped) str(true) if is_looped_req
http-request use-service lua.modify_request if !skip_all { var(req.lunar.modify_request) -m bool } !{ var(req.is_looped) -m found }
http-response wait-for-body time 10000 if !skip_all is_managed body_required # Max time to wait for response body is 10 seconds
http-response send-spoe-group lunar lunar-response-group if !is_res_error !skip_all !body_required !is_early_response is_managed ! { var(txn.lua_handled) -m found }
http-response send-spoe-group lunar lunar-full-response-group if !is_res_error !skip_all !is_early_response is_managed body_required ! { var(txn.lua_handled) -m found }
# Modify headers
http-request lua.modify_headers if !skip_all { var(req.lunar.modify_headers) -m bool }
http-request use-service lua.generate_request if !skip_all { var(req.lunar.generate_request) -m bool }
# re-resolve the host after modification
http-request do-resolve(req.host_ip,resolv-conf,ipv4) var(txn.host),host_only
http-request set-var(txn.x_lunar_error) str(5) unless { var(req.host_ip) -m found }
http-request set-var(txn.error_in_body) str("Could not resolve host") unless { var(req.host_ip) -m found }
http-request deny status 503 content-type text/plain lf-string "Could not resolve host" hdr x-lunar-error 5 unless { var(req.host_ip) -m found }
# Received an early response from Lunar
acl is_resp_internal var(txn.lunar.is_internal) -m bool
http-request set-var(txn.is_internal) str("true") if is_resp_internal
http-request use-service lua.mock_response if !skip_all is_early_response
http-request set-dst var(req.host_ip) # Set new destination IP
http-request set-dst-port var(txn.dst_port)
# Remove headers starting with x-lunar
# ***Attention***: this is done right before sending request to provider
# In the case we would need to refer to request headers in the response lifecycle,
# said header should be extracted and stored in a variable before this point
http-request del-header x-lunar -m beg
# Send request to provider
use_backend %[var(txn.host)] if use_mtls
use_backend provider if is_https_scheme !use_mtls
default_backend insecure_provider
# Modify response
http-response lua.retry_request if !skip_all { var(res.lunar.retry_request) -m bool }
http-response lua.modify_response if !skip_all { var(res.lunar.modify_response) -m bool }
acl is_resp_internal var(res.lunar.is_internal) -m bool
http-response set-var(txn.is_internal) str("true") if is_resp_internal
# Update the endpoints are managed by Lunar
frontend endpoints
bind *:${HAPROXY_MANAGE_ENDPOINTS_PORT}
mode http
option http-buffer-request # Buffer the request to allow reading body
acl method_get method GET
acl method_put method PUT
acl method_delete method DELETE
http-request deny status 405 unless method_get or method_put or method_delete
acl path_manage_all path /manage_all
acl path_managed_endpoint path /managed_endpoint
acl path_unmanage_all path /unmanage_all
acl path_unmanage_global path /unmanage_global
acl path_include_body path /include_body_from
acl path_include_body_to_all path /include_body_from_all
acl path_remove_body_from_all path /remove_body_from_all
acl path_capture_req path /capture_req_from
acl path_capture_req_all path /capture_req_all
acl path_stop_capturing_req_all path /stop_capturing_req_all
http-request deny status 404 unless path_manage_all or path_managed_endpoint or path_unmanage_all or path_unmanage_global or path_include_body or path_managed_endpoint or path_include_body_to_all or path_remove_body_from_all or path_capture_req or path_capture_req_all or path_stop_capturing_req_all
acl body_found req.body -m found
http-request deny status 400 if path_managed_endpoint !body_found or path_include_body !body_found or path_capture_req !body_found
use_backend get_manage_all if method_get path_manage_all
use_backend get_unmanage_all if method_get path_unmanage_all
use_backend get_managed_endpoint if method_get path_managed_endpoint body_found
use_backend manage_all if method_put path_manage_all
use_backend unmanage_all if method_put path_unmanage_all
use_backend manage_endpoint if method_put path_managed_endpoint body_found
use_backend unmanage_endpoint if method_delete path_managed_endpoint body_found
use_backend unmanage_global if method_delete path_unmanage_global
use_backend include_body_from if method_put path_include_body
use_backend remove_body_from if method_delete path_include_body
use_backend include_body_from_all if method_put path_include_body_to_all
use_backend remove_body_from_all if method_put path_remove_body_from_all
use_backend capture_req_from if method_put path_capture_req
use_backend stop_capturing_req_from if method_delete path_capture_req
use_backend capture_req_from_all if method_put path_capture_req_all
use_backend stop_capturing_req_from_all if method_put path_stop_capturing_req_all
# Informative endpoints
backend get_manage_all
mode http
acl manage_all var(proc.manage_all) -m found
http-request set-var(txn.resp_body) str(true) if manage_all
http-request set-var(txn.resp_body) str(false) unless manage_all
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend get_unmanage_all
mode http
acl skip_all var(proc.skip_all) -m found
http-request set-var(txn.resp_body) str(true) if skip_all
http-request set-var(txn.resp_body) str(false) unless skip_all
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend get_managed_endpoint
mode http
acl endpoint_is_managed req.body,map_reg(/etc/haproxy/maps/endpoints.map) -m found
http-request set-var(txn.resp_body) str(true) if endpoint_is_managed
http-request set-var(txn.resp_body) str(false) unless endpoint_is_managed
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
# Operational endpoints
backend manage_all
mode http
http-request unset-var(proc.skip_all)
http-request set-var(proc.manage_all) str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend manage_endpoint
mode http
http-request unset-var(proc.skip_all)
http-request set-map(/etc/haproxy/maps/endpoints.map) %[req.body] str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend unmanage_endpoint
mode http
http-request del-map(/etc/haproxy/maps/endpoints.map) %[req.body]
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend unmanage_all
mode http
http-request unset-var(proc.manage_all)
http-request set-var(proc.skip_all) str(true)
http-request del-map(/etc/haproxy/maps/endpoints.map) .
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
# Unlike unmanage_all, unmanage_global does not remove the endpoints from the map
backend unmanage_global
mode http
http-request unset-var(proc.manage_all)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend include_body_from
mode http
http-request set-map(/etc/haproxy/maps/spoe_with_body.map) %[req.body] str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend include_body_from_all
mode http
http-request set-var(proc.body_from_all) str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend remove_body_from
mode http
http-request del-map(/etc/haproxy/maps/spoe_with_body.map) %[req.body]
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
# This is not used yet, but it is here for future use
backend remove_body_from_all
mode http
http-request del-map(/etc/haproxy/maps/spoe_with_body.map) .
http-request unset-var(proc.body_from_all)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend capture_req_from
mode http
http-request set-map(/etc/haproxy/maps/req_capture.map) %[req.body] str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend capture_req_from_all
mode http
http-request set-var(proc.capture_all) str(true)
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
backend stop_capturing_req_from
mode http
http-request del-map(/etc/haproxy/maps/req_capture.map) %[req.body]
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
# This is not used yet, but it is here for future use
backend stop_capturing_req_from_all
mode http
http-request unset-var(proc.capture_all)
http-request del-map(/etc/haproxy/maps/req_capture.map) .
http-request set-var(txn.resp_body) str(true)
http-request return status 200 content-type text/plain lf-string "%[var(txn.resp_body)]"
# Backend used by the SPOE
backend lunar
mode tcp
timeout connect 20s # greater than hello timeout
timeout server "${LUNAR_SPOE_PROCESSING_TIMEOUT_SEC}"s
option spop-check
server agent localhost:12345
backend provider
mode http
server clear 0.0.0.0:0 check-ssl ssl check-sni var(txn.host),host_only sni var(txn.host),host_only verify none
backend insecure_provider
mode http
server clear 0.0.0.0:0
backend async_service
mode http
server steve 127.0.0.1:${ASYNC_SERVICE_PORT}
# DNS
resolvers resolv-conf
parse-resolv-conf