Skip to content

Commit f781dfe

Browse files
authored
escalate: Add x-escalate-redirect header (#1092) (#12447)
If the escalate.so plugin escalates a request, add the x-escalate-redirect header as an indicator that it did so. Adding the header can be disabled via: @pparam=--no-redirect-header
1 parent 6610bd8 commit f781dfe

3 files changed

Lines changed: 85 additions & 18 deletions

File tree

doc/admin-guide/plugins/escalate.en.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ when the origin server in the remap rule returns a 401,
4343
This option sends the "pristine" Host: header (eg, the Host: header
4444
that the client sent) to the escalated request.
4545

46+
@pparam=--no-redirect-header
47+
Controls whether to add the x-escalate-redirect header to escalated requests.
48+
When enabled (default), the plugin adds an x-escalate-redirect header with value "1"
49+
to the client request when it escalates to a different origin server. This header
50+
can be used by downstream systems to identify requests that have been escalated.
51+
The header is only added if it doesn't already exist in the request.
52+
Use --no-redirect-header to disable adding the x-escalate-redirect header.
53+
4654
@pparam=--escalate-non-get-methods
4755
In general, the escalate plugin is used with a failover origin that serves a
4856
cached backup of the original content. As a result, the default behavior is
@@ -68,10 +76,17 @@ With this line in :file:`remap.config` ::
6876
Traffic Server would accept a request for ``cdn.example.com`` and, on a cache miss, proxy the
6977
request to ``origin.example.com``. If the response code from that server is a 401, 404, 410,
7078
or 502, then Traffic Server would proxy the request to ``second-origin.example.com``, using a
71-
Host: header of ``cdn.example.com``.
79+
Host: header of ``cdn.example.com``. Additionally, an x-escalate-redirect header with value "1"
80+
will be added to the escalated request.
81+
82+
To disable adding the x-escalate-redirect header, use::
83+
84+
map cdn.example.com origin.example.com \
85+
@plugin=escalate.so @pparam=401,404,410,502:second-origin.example.com @pparam=--no-redirect-header
7286

7387
By default, only GET requests are escalated. To escalate non-GET requests as
7488
well, you can use::
7589

7690
map cdn.example.com origin.example.com \
7791
@plugin=escalate.so @pparam=401,404,410,502:second-origin.example.com @pparam=--escalate-non-get-methods
92+

plugins/escalate/escalate.cc

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838

3939
// Constants and some declarations
4040

41-
const char PLUGIN_NAME[] = "escalate";
41+
const char PLUGIN_NAME[] = "escalate";
42+
const char REDIRECT_HEADER[] = "x-escalate-redirect";
4243

4344
static DbgCtl dbg_ctl{PLUGIN_NAME};
4445

@@ -72,6 +73,7 @@ struct EscalationState {
7273
StatusMapType status_map;
7374
bool use_pristine = false;
7475
bool escalate_non_get_methods = false;
76+
bool add_redirect_header = true;
7577
};
7678

7779
// Little helper function, to update the Host portion of a URL, and stringify the result.
@@ -208,6 +210,30 @@ EscalateResponse(TSCont cont, TSEvent event, void *edata)
208210
// Now update the Redirect URL, if set
209211
if (url_str) {
210212
TSHttpTxnRedirectUrlSet(txn, url_str, url_len); // Transfers ownership
213+
214+
// Add our x-escalate-redirect header marker if it doesn't already exist and the option is enabled.
215+
if (es->add_redirect_header) {
216+
TSMBuffer bufp;
217+
TSMLoc hdr_loc, field_loc;
218+
219+
if (TS_SUCCESS == TSHttpTxnClientReqGet(txn, &bufp, &hdr_loc)) {
220+
field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, REDIRECT_HEADER, sizeof(REDIRECT_HEADER) - 1);
221+
if (field_loc == nullptr) {
222+
if (TSMimeHdrFieldCreateNamed(bufp, hdr_loc, REDIRECT_HEADER, sizeof(REDIRECT_HEADER) - 1, &field_loc) == TS_SUCCESS) {
223+
if (TSMimeHdrFieldValueStringInsert(bufp, hdr_loc, field_loc, -1, "1", 1) == TS_SUCCESS) {
224+
TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
225+
}
226+
TSHandleMLocRelease(bufp, hdr_loc, field_loc);
227+
Dbg(dbg_ctl, "Added x-escalate-redirect header to the client request.");
228+
}
229+
} else {
230+
Dbg(dbg_ctl, "x-escalate-redirect header already exists, not adding.");
231+
}
232+
TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
233+
} else {
234+
TSError("[%s] Failed to get client request header to add x-escalate-redirect header", PLUGIN_NAME);
235+
}
236+
}
211237
}
212238

213239
// Set the transaction free ...
@@ -234,6 +260,8 @@ TSRemapNewInstance(int argc, char *argv[], void **instance, char *errbuf, int er
234260
// Ugly, but we set the precedence before with non-command line parsing of args
235261
if (0 == strncasecmp(argv[i], "--pristine", 10)) {
236262
es->use_pristine = true;
263+
} else if (0 == strncasecmp(argv[i], "--no-redirect-header", 20)) {
264+
es->add_redirect_header = false;
237265
} else if (0 == strncasecmp(argv[i], "--escalate-non-get-methods", 26)) {
238266
es->escalate_non_get_methods = true;
239267
} else {

tests/gold_tests/pluginTest/escalate/escalate.test.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,39 @@ class EscalateTest:
3434

3535
_replay_original_file: str = 'escalate_original.replay.yaml'
3636
_replay_failover_file: str = 'escalate_failover.replay.yaml'
37+
_process_counter: int = 0
3738

38-
def __init__(self):
39-
'''Configure the test run.'''
40-
tr = Test.AddTestRun('Test escalate plugin.')
39+
def __init__(self, disable_redirect_header: bool = False) -> None:
40+
'''Configure the test run.
41+
:param disable_redirect_header: Whether to use --no-redirect-header.
42+
'''
43+
tr = Test.AddTestRun(f'Test escalate plugin. disable_redirect_header={disable_redirect_header}')
4144
self._setup_dns(tr)
42-
self._setup_servers(tr)
43-
self._setup_ts(tr)
45+
self._setup_servers(tr, disable_redirect_header)
46+
self._setup_ts(tr, disable_redirect_header)
4447
self._setup_client(tr)
48+
EscalateTest._process_counter += 1
4549

46-
def _setup_dns(self, tr: 'Process') -> None:
50+
def _setup_dns(self, tr: 'TestRun') -> None:
4751
'''Set up the DNS server.
4852
4953
:param tr: The test run to add the DNS server to.
5054
'''
51-
self._dns = tr.MakeDNServer(f"dns", default='127.0.0.1')
55+
process_name = f"dns_{EscalateTest._process_counter}"
56+
self._dns = tr.MakeDNServer(process_name, default='127.0.0.1')
5257

53-
def _setup_servers(self, tr: 'Process') -> None:
58+
def _setup_servers(self, tr: 'TestRun', disable_redirect_header: bool) -> None:
5459
'''Set up the origin and failover servers.
5560
5661
:param tr: The test run to add the servers to.
62+
:param disable_redirect_header: Whether ATS was configured with --no-redirect-header.
5763
'''
5864
tr.Setup.Copy(self._replay_original_file)
5965
tr.Setup.Copy(self._replay_failover_file)
60-
self._server_origin = tr.AddVerifierServerProcess(f"server_origin", self._replay_original_file)
61-
self._server_failover = tr.AddVerifierServerProcess(f"server_failover", self._replay_failover_file)
66+
process_name = f"server_origin_{EscalateTest._process_counter}"
67+
self._server_origin = tr.AddVerifierServerProcess(process_name, self._replay_original_file)
68+
process_name = f"server_failover_{EscalateTest._process_counter}"
69+
self._server_failover = tr.AddVerifierServerProcess(process_name, self._replay_failover_file)
6270

6371
self._server_origin.Streams.All += Testers.ContainsExpression(
6472
'uuid: GET', "Verify the origin server received the GET request.")
@@ -72,6 +80,8 @@ def _setup_servers(self, tr: 'Process') -> None:
7280
'uuid: POST_fail_not_escalated', "Verify the origin server received the POST request that should not be escalated.")
7381
self._server_origin.Streams.All += Testers.ExcludesExpression(
7482
'uuid: GET_down_origin', "Verify the origin server did not receive the down origin request.")
83+
self._server_origin.Streams.All += Testers.ExcludesExpression(
84+
'x-escalate-redirect', "Verify the origin server should never receive the x-escalate-redirect header.")
7585

7686
self._server_failover.Streams.All += Testers.ContainsExpression(
7787
'uuid: GET_failed', "Verify the failover server received the failed GET request.")
@@ -90,12 +100,21 @@ def _setup_servers(self, tr: 'Process') -> None:
90100
'uuid: POST_fail_not_escalated',
91101
"Verify the failover server did not receive the POST request that should not be escalated.")
92102

93-
def _setup_ts(self, tr: 'Process') -> None:
103+
if disable_redirect_header:
104+
self._server_failover.Streams.All += Testers.ExcludesExpression(
105+
'x-escalate-redirect', "Verify the failover server did not receive the x-escalate-redirect header.")
106+
else:
107+
self._server_failover.Streams.All += Testers.ContainsExpression(
108+
'x-escalate-redirect: 1', "Verify the failover server received the x-escalate-redirect header.")
109+
110+
def _setup_ts(self, tr: 'Process', disable_redirect_header: bool) -> None:
94111
'''Set up Traffic Server.
95112
96113
:param tr: The test run to add Traffic Server to.
114+
:param disable_redirect_header: Whether ATS should be configured with --no-redirect-header.
97115
'''
98-
self._ts = tr.MakeATSProcess(f"ts", enable_cache=False)
116+
process_name = f"ts_{EscalateTest._process_counter}"
117+
self._ts = tr.MakeATSProcess(process_name, enable_cache=False)
99118
# Select a port that is guaranteed to not be used at the moment.
100119
dead_port = get_port(self._ts, "dead_port")
101120
self._ts.Disk.records_config.update(
@@ -107,23 +126,27 @@ def _setup_ts(self, tr: 'Process') -> None:
107126
'proxy.config.http.redirect.actions': 'self:follow',
108127
'proxy.config.http.number_of_redirections': 4,
109128
})
129+
params = ''
130+
if disable_redirect_header:
131+
params = '@pparam=--no-redirect-header'
110132
self._ts.Disk.remap_config.AddLines(
111133
[
112134
f'map http://origin.server.com http://backend.origin.server.com:{self._server_origin.Variables.http_port} '
113-
f'@plugin=escalate.so @pparam=500,502:failover.server.com:{self._server_failover.Variables.http_port}',
135+
f'@plugin=escalate.so @pparam=500,502:failover.server.com:{self._server_failover.Variables.http_port} {params}',
114136

115137
# Now create remap entries for the multiplexed hosts: one that
116138
# verifies HTTP, and another that verifies HTTPS.
117139
f'map http://down_origin.server.com http://backend.down_origin.server.com:{dead_port} '
118-
f'@plugin=escalate.so @pparam=500,502:failover.server.com:{self._server_failover.Variables.http_port} ',
140+
f'@plugin=escalate.so @pparam=500,502:failover.server.com:{self._server_failover.Variables.http_port} {params}',
119141
])
120142

121143
def _setup_client(self, tr: 'Process') -> None:
122144
'''Set up the client.
123145
124146
:param tr: The test run to add the client to.
125147
'''
126-
client = tr.AddVerifierClientProcess(f"client", self._replay_original_file, http_ports=[self._ts.Variables.port])
148+
process_name = f"client_{EscalateTest._process_counter}"
149+
client = tr.AddVerifierClientProcess(process_name, self._replay_original_file, http_ports=[self._ts.Variables.port])
127150
client.StartBefore(self._dns)
128151
client.StartBefore(self._server_origin)
129152
client.StartBefore(self._server_failover)
@@ -254,5 +277,6 @@ def _setup_client(self, tr: 'TestRun') -> None:
254277
client.Streams.All += Testers.ExcludesExpression(r'\[ERROR\]', 'Verify there were no errors in the replay.')
255278

256279

257-
EscalateTest()
280+
EscalateTest(disable_redirect_header=False)
281+
EscalateTest(disable_redirect_header=True)
258282
EscalateNonGetMethodsTest()

0 commit comments

Comments
 (0)