Skip to content
Open
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
98 changes: 62 additions & 36 deletions doc/admin-guide/configuration/hrw4u.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,42 +174,46 @@ Conditions

Below is a partial mapping of `header_rewrite` condition symbols to their HRW4U equivalents:

================================ ================================== ================================================
Header Rewrite HRW4U Description
================================ ================================== ================================================
cond %{ACCESS:/path} access("/path") File exists at "/path" and is accessible by ATS
cond %{CACHE} =hit-fresh cache() == "hit-fresh" Cache lookup result status
cond %{CIDR:24,48} =ip cidr(24,48) == "ip" Match masked client IP address
cond %{CLIENT-HEADER:X} =foo inbound.req.X == "foo" Original client request header
cond %{CLIENT-URL:<C>} =bar inbound.url.<C> == "bar" URL component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host``, ``path`` etc.
cond %{COOKIE:foo} =bar {in,out}bound.cookie.foo == "bar" Check a cookie value
cond %{FROM-URL:<C>} =bar from.url.<C> == "bar" Remap ``From URL`` component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{HEADER:X} =fo {in,out}bound.{req,resp}.X == "fo" Context sensitive header conditions
cond %{ID:UNIQUE} =... id.UNIQUE == "..." (:ref:`Unique/request/process<admin-plugins-header-rewrite-id>`) transaction identifier
cond %{INTERNAL-TRANSACTION} internal() Check if transaction is internally generated
cond %{INBOUND:CLIENT-CERT:<X>} inbound.client-cert.<X> Access the mTLS / client certificate details, on the inbound (client) connection
cond %{INBOUND:SERVER-CERT:<X>} inbound.client-cert.<X> Access the server (handshake) certificate details, on the inbound connection
cond %{IP:CLIENT} ="..." inbound.ip == "..." Client's IP address. Same as ``inbound.REMOTE_ADDR``
cond %{IP:INBOUND} ="..." inbound.server == "..." ATS's IP address to which the client connected
cond %{IP:SERVER} ="..." outbound.ip == "..." Upstream (next-hop) server IP address
cond %{IP:OUTBOUND} ="..." outbound.server == "..." ATS's outbound IP address, connecting upstream
cond %{LAST-CAPTURE:<#>} ="..." capture.<#> == "..." Last capture group from regex match (range: `0-9`)
cond %{METHOD} =GET inbound.method == "GET" HTTP method match
cond %{NEXT-HOP:<C>} ="bar" outbound.url.<C> == "bar" Next-hop URL component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{NOW:<U>} ="..." now.<U> == "..." Current date/time in format, <:ref:`U<admin-plugins-header-rewrite-geo>`> selects time unit
cond %{OUTBOUND:CLIENT-CERT:<X>} outbound.client-cert.<X> Access the mTLS / client certificate details, on the outbound (upstream) connection
cond %{OUTbOUND:SERVER-CERT:<X>} outbound.client-cert.<X> Access the server (handshake) certificate details, on the outbound connection
cond %{RANDOM:500} >250 random(500) > 250 Random number between 0 and the specified range
cond %{SSN-TXN-COUNT} >10 ssn-txn-count() > 10 Number of transactions on server connection
cond %{TO-URL:<C>} =bar to.url.<C> == "bar" Remap ``To URL`` component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{TXN-COUNT} >10 txn-count() > 10 Number of transactions on client connection
cond %{URL:<C> =bar {in,out}bound.url.<C> == "bar" Context aware URL component match
cond %{GEO:<C>} =bar geo.<C> == "bar" IP to Geo mapping. <:ref:`C<admin-plugins-header-rewrite-geo>`> is country, asn, etc.
cond %{STATUS} =200 inbound.status ==200 Origin http status code
cond %{TCP-INFO} tcp.info TCP Info struct field values
cond %{HTTP-CNTL:<C>} http.cntl.<C> Check the state of the <:ref:`C<admin-plugins-header-rewrite-set-http-cntl>`> HTTP control
cond %{INBOUND:<C>} {in,out}bound.conn.<c> inbound (:ref:`client, user agent<admin-plugins-header-rewrite-inbound>`) connection to ATS
================================ ================================== ================================================
================================= ================================== ================================================
Header Rewrite HRW4U Description
================================= ================================== ================================================
cond %{ACCESS:/path} access("/path") File exists at "/path" and is accessible by ATS
cond %{CACHE} =hit-fresh cache() == "hit-fresh" Cache lookup result status
cond %{CIDR:24,48} =ip cidr(24,48) == "ip" Match masked client IP address
cond %{CLIENT-HEADER:X} =foo inbound.req.X == "foo" Original client request header
cond %{CLIENT-URL:<C>} =bar inbound.url.<C> == "bar" URL component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host``, ``path`` etc.
cond %{CLIENT-URL:QUERY:<P>} =bar inbound.url.query.<P> == "bar" Extract specific query parameter ``P`` from URL
cond %{COOKIE:foo} =bar {in,out}bound.cookie.foo == "bar" Check a cookie value
cond %{FROM-URL:<C>} =bar from.url.<C> == "bar" Remap ``From URL`` component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{FROM-URL:QUERY:<P>} =bar from.url.query.<P> == "bar" Extract specific query parameter ``P`` from remap ``From URL``
cond %{HEADER:X} =fo {in,out}bound.{req,resp}.X == "fo" Context sensitive header conditions
cond %{ID:UNIQUE} =... id.UNIQUE == "..." (:ref:`Unique/request/process<admin-plugins-header-rewrite-id>`) transaction identifier
cond %{INTERNAL-TRANSACTION} internal() Check if transaction is internally generated
cond %{INBOUND:CLIENT-CERT:<X>} inbound.client-cert.<X> Access the mTLS / client certificate details, on the inbound (client) connection
cond %{INBOUND:SERVER-CERT:<X>} inbound.client-cert.<X> Access the server (handshake) certificate details, on the inbound connection
cond %{IP:CLIENT} ="..." inbound.ip == "..." Client's IP address. Same as ``inbound.REMOTE_ADDR``
cond %{IP:INBOUND} ="..." inbound.server == "..." ATS's IP address to which the client connected
cond %{IP:SERVER} ="..." outbound.ip == "..." Upstream (next-hop) server IP address
cond %{IP:OUTBOUND} ="..." outbound.server == "..." ATS's outbound IP address, connecting upstream
cond %{LAST-CAPTURE:<#>} ="..." capture.<#> == "..." Last capture group from regex match (range: `0-9`)
cond %{METHOD} =GET inbound.method == "GET" HTTP method match
cond %{NEXT-HOP:<C>} ="bar" outbound.url.<C> == "bar" Next-hop URL component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{NEXT-HOP:QUERY:<P>} =bar outbound.url.query.<P> == "bar" Extract specific query parameter ``P`` from next-hop URL
cond %{NOW:<U>} ="..." now.<U> == "..." Current date/time in format, <:ref:`U<admin-plugins-header-rewrite-geo>`> selects time unit
cond %{OUTBOUND:CLIENT-CERT:<X>} outbound.client-cert.<X> Access the mTLS / client certificate details, on the outbound (upstream) connection
cond %{OUTbOUND:SERVER-CERT:<X>} outbound.client-cert.<X> Access the server (handshake) certificate details, on the outbound connection
cond %{RANDOM:500} >250 random(500) > 250 Random number between 0 and the specified range
cond %{SSN-TXN-COUNT} >10 ssn-txn-count() > 10 Number of transactions on server connection
cond %{TO-URL:<C>} =bar to.url.<C> == "bar" Remap ``To URL`` component match, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is ``host`` etc.
cond %{TO-URL:QUERY:<P>} =bar to.url.query.<P> == "bar" Extract specific query parameter ``P`` from remap ``To URL``
cond %{TXN-COUNT} >10 txn-count() > 10 Number of transactions on client connection
cond %{URL:<C> =bar {in,out}bound.url.<C> == "bar" Context aware URL component match
cond %{GEO:<C>} =bar geo.<C> == "bar" IP to Geo mapping. <:ref:`C<admin-plugins-header-rewrite-geo>`> is country, asn, etc.
cond %{STATUS} =200 inbound.status ==200 Origin http status code
cond %{TCP-INFO} tcp.info TCP Info struct field values
cond %{HTTP-CNTL:<C>} http.cntl.<C> Check the state of the <:ref:`C<admin-plugins-header-rewrite-set-http-cntl>`> HTTP control
cond %{INBOUND:<C>} {in,out}bound.conn.<c> inbound (:ref:`client, user agent<admin-plugins-header-rewrite-inbound>`) connection to ATS
================================= ================================== ================================================

The conditions operating on headers and URLs are also available as operators. E.g.:

Expand Down Expand Up @@ -698,6 +702,28 @@ limiting to the request.::
}
}

Route Based on Query Parameter Value
------------------------------------

This rule extracts a specific query parameter value and uses it to make routing
decisions or set custom headers. The ``query.<param_name>`` syntax allows
extracting individual query parameter values::

REMAP {
if inbound.url.query.version == "v2" {
inbound.req.X-API-Version = "v2";
}
}

SEND_RESPONSE {
inbound.resp.X-Debug-Param = "{inbound.url.query.debug}";
}

.. note::
Query parameter names are case-sensitive and matched as-is without URL
decoding. For example, ``inbound.url.query.my%20param`` matches the literal
parameter name ``my%20param``, not ``my param``.

References
==========

Expand Down
13 changes: 8 additions & 5 deletions tools/hrw4u/src/hrw_symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,15 @@ def percent_to_ident_or_func(self, percent: str, section: SectionType | None) ->
tag = match.group(1)
payload = match.group(2)

# Handle certificate tags explicitly to ensure proper parsing
# Handle multi-colon tags explicitly to ensure proper parsing
# Multi-colon parsing for certificate tags (CLIENT-CERT, SERVER-CERT) and query parameter tags (QUERY)
original_inner = percent[2:-1]
if ":" in original_inner and any(cert_tag in original_inner for cert_tag in ["CLIENT-CERT", "SERVER-CERT"]):
new_tag, new_payload = self.parse_percent_block(percent)
if new_tag != tag or new_payload != payload:
tag, payload = new_tag, new_payload
if ":" in original_inner:
if (any(cert_tag in original_inner for cert_tag in ["CLIENT-CERT", "SERVER-CERT"]) or
(payload and payload.startswith("QUERY:"))):
new_tag, new_payload = self.parse_percent_block(percent)
if new_tag != tag or new_payload != payload:
tag, payload = new_tag, new_payload

if types.BooleanLiteral.contains(tag):
return tag, False
Expand Down
10 changes: 9 additions & 1 deletion tools/hrw4u/src/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@

# Prefix matches
"capture.": MapParams(target="LAST-CAPTURE", prefix=True, validate=Validator.range(0, 9)),
"from.url.query.": MapParams(target="FROM-URL:QUERY", prefix=True, validate=Validator.http_token(), sections=HTTP_SECTIONS),
"from.url.": MapParams(target="FROM-URL", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS), sections=HTTP_SECTIONS),
"geo.": MapParams(target="GEO", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.GEO_FIELDS)),
"http.cntl.": MapParams(target="HTTP-CNTL", upper=True, validate=Validator.suffix_group(SuffixGroup.HTTP_CNTL_FIELDS), sections=HTTP_SECTIONS),
Expand All @@ -110,6 +111,7 @@
"inbound.cookie.": MapParams(target="COOKIE", prefix=True, validate=Validator.http_token(), sections=HTTP_SECTIONS, rev={"reverse_fallback": "inbound.cookie."}),
"inbound.req.": MapParams(target="CLIENT-HEADER", prefix=True, validate=Validator.http_header_name(), sections=HTTP_SECTIONS, rev={"reverse_fallback": "inbound.req."}),
"inbound.resp.": MapParams(target="HEADER", prefix=True, validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_context": "header_condition"}),
"inbound.url.query.": MapParams(target="CLIENT-URL:QUERY", prefix=True, validate=Validator.http_token(), sections=HTTP_SECTIONS),
"inbound.url.": MapParams(target="CLIENT-URL", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS), sections=HTTP_SECTIONS),
"now.": MapParams(target="NOW", upper=True, validate=Validator.suffix_group(SuffixGroup.DATE_FIELDS)),
"outbound.conn.client-cert.SAN.": MapParams(target="OUTBOUND:CLIENT-CERT:SAN", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS), sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
Expand All @@ -122,7 +124,9 @@
"outbound.cookie.": MapParams(target="COOKIE", prefix=True, validate=Validator.http_token(), sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_fallback": "inbound.cookie."}),
"outbound.req.": MapParams(target="HEADER", prefix=True, validate=Validator.http_header_name(), sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_context": "header_condition"}),
"outbound.resp.": MapParams(target="HEADER", prefix=True, validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_context": "header_condition"}),
"outbound.url.query.": MapParams(target="NEXT-HOP:QUERY", prefix=True, validate=Validator.http_token(), sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST, SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
"outbound.url.": MapParams(target="NEXT-HOP", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS), sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST, SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
"to.url.query.": MapParams(target="TO-URL:QUERY", prefix=True, validate=Validator.http_token(), sections=HTTP_SECTIONS),
"to.url.": MapParams(target="TO-URL", upper=True, prefix=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS), sections=HTTP_SECTIONS),
}

Expand All @@ -137,7 +141,11 @@
"OUTBOUND:CLIENT-CERT": ("outbound.conn.client-cert.", False),
"OUTBOUND:SERVER-CERT": ("outbound.conn.server-cert.", False),
"OUTBOUND:CLIENT-CERT:SAN": ("outbound.conn.client-cert.SAN.", False),
"OUTBOUND:SERVER-CERT:SAN": ("outbound.conn.server-cert.SAN.", False)
"OUTBOUND:SERVER-CERT:SAN": ("outbound.conn.server-cert.SAN.", False),
"CLIENT-URL:QUERY": ("inbound.url.query.", False),
"NEXT-HOP:QUERY": ("outbound.url.query.", False),
"FROM-URL:QUERY": ("from.url.query.", False),
"TO-URL:QUERY": ("to.url.query.", False)
}

# Context type to mapping name associations
Expand Down
1 change: 1 addition & 0 deletions tools/hrw4u/tests/data/conds/query-param.ast.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(program (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.url.query.version) == (value "v2")))))) (block { (blockItem (statement inbound.req.X-API-Version = (value "v2") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable from.url.query.source) == (value "mobile")))))) (block { (blockItem (statement inbound.req.X-Source = (value "mobile") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor to.url.query.target)))) (block { (blockItem (statement inbound.req.X-Target = (value "set") ;)) })))) })) (programItem (section SEND_REQUEST { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable outbound.url.query.backend) == (value "fast")))))) (block { (blockItem (statement outbound.req.X-Priority = (value "high") ;)) })))) })) (programItem (section SEND_RESPONSE { (sectionBody (statement inbound.resp.X-Query-Sub = (value "{inbound.url.query.sub}") ;)) (sectionBody (statement inbound.resp.X-From-Param = (value "{from.url.query.param}") ;)) (sectionBody (statement inbound.resp.X-To-Param = (value "{to.url.query.redirect}") ;)) })) <EOF>)
25 changes: 25 additions & 0 deletions tools/hrw4u/tests/data/conds/query-param.input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
REMAP {
if inbound.url.query.version == "v2" {
inbound.req.X-API-Version = "v2";
}

if from.url.query.source == "mobile" {
inbound.req.X-Source = "mobile";
}

if to.url.query.target {
inbound.req.X-Target = "set";
}
}

SEND_REQUEST {
if outbound.url.query.backend == "fast" {
outbound.req.X-Priority = "high";
}
}

SEND_RESPONSE {
inbound.resp.X-Query-Sub = "{inbound.url.query.sub}";
inbound.resp.X-From-Param = "{from.url.query.param}";
inbound.resp.X-To-Param = "{to.url.query.redirect}";
}
20 changes: 20 additions & 0 deletions tools/hrw4u/tests/data/conds/query-param.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cond %{REMAP_PSEUDO_HOOK} [AND]
cond %{CLIENT-URL:QUERY:version} ="v2"
set-header X-API-Version "v2"

cond %{REMAP_PSEUDO_HOOK} [AND]
cond %{FROM-URL:QUERY:source} ="mobile"
set-header X-Source "mobile"

cond %{REMAP_PSEUDO_HOOK} [AND]
cond %{TO-URL:QUERY:target} ="" [NOT]
set-header X-Target "set"

cond %{SEND_REQUEST_HDR_HOOK} [AND]
cond %{NEXT-HOP:QUERY:backend} ="fast"
set-header X-Priority "high"

cond %{SEND_RESPONSE_HDR_HOOK} [AND]
set-header X-Query-Sub "%{CLIENT-URL:QUERY:sub}"
set-header X-From-Param "%{FROM-URL:QUERY:param}"
set-header X-To-Param "%{TO-URL:QUERY:redirect}"