Skip to content

Commit 9da5133

Browse files
author
Kai Wu
committed
en translation for execution order 10
1 parent 6d3c215 commit 9da5133

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
= Nginx directive execution order (10) =
2+
3+
After C<post-rewrite>, it is the C<preaccess> phase.
4+
Just as its name implies, the phase is called C<preaccess>
5+
simply because it is executed right before C<access> phase.
6+
7+
Built-in module L<ngx_limit_req> and L<ngx_limit_zone> are
8+
executed in this phase. The former limits the number of
9+
requests per hour/minute, and the latter limits the number
10+
of simultaneous requests. We will be discussing them more
11+
thoroughly afterwards.
12+
13+
Actually, built-in module L<ngx_realip> registers its
14+
handler in C<preaccess> as well. You might need to ask
15+
then: "why do it again? Did it register its handlers in
16+
C<post-read> phase already". Before the answer is uncovered
17+
let's study following example:
18+
19+
:nginx
20+
server {
21+
listen 8080;
22+
23+
location /test {
24+
set_real_ip_from 127.0.0.1;
25+
real_ip_header X-Real-IP;
26+
27+
echo "from: $remote_addr";
28+
}
29+
}
30+
31+
Comparing to the earlier example, the major difference is
32+
that commands of module L<ngx_realip> are written in a
33+
specific C<location> directive. As we have learnt before,
34+
Nginx matches its C<location> directives in C<find-config>
35+
phase, which is far behind C<post-read>, hence the request
36+
has nothing to do with commands written in any C<location>
37+
directive in C<post-read> phase. Back to our example,
38+
it is exactly the case where commands are written in a
39+
C<location> directive and module L<ngx_realip> won't carry
40+
out any rewrite of the remote address, because it is not
41+
instructed as such in C<post-read> phase.
42+
43+
What if we do need the rewrite? To help resolve the issue,
44+
module L<ngx_realip> registers its handlers in C<preaccess>
45+
again, so that it is given the chance to execute in a
46+
C<location> directive. Now the example runs as we would've
47+
expected:
48+
49+
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
50+
from: 1.2.3.4
51+
52+
Be really careful though, module L<ngx_realip> could easily
53+
be misused, as our following example illustrates:
54+
55+
:nginx
56+
server {
57+
listen 8080;
58+
59+
location /test {
60+
set_real_ip_from 127.0.0.1;
61+
real_ip_header X-Real-IP;
62+
63+
set $addr $remote_addr;
64+
echo "from: $addr";
65+
}
66+
}
67+
68+
In the example, we introduces a variable C<$addr>, to which
69+
the value of L<ngx_core/$remote_addr> is saved in C<rewrite>
70+
phase. The variable is then used in the output. Slow down
71+
right here and you might have noticed the issue, phase C<rewrite>
72+
occurs earlier than C<preaccess>, so variable assignment
73+
actually happens before module L<ngx_realip> has the chance
74+
to rewrite the remote address in C<preaccess> phase. The
75+
output proves our observation:
76+
77+
:bash
78+
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
79+
from: 127.0.0.1
80+
81+
The output gives the actual remote address (not the rewritten one)
82+
Again Nginx "debug log" helps assert it too:
83+
84+
:bash
85+
$ grep -E 'http script (var|set)|realip' logs/error.log
86+
[debug] 32488#0: *1 http script var: "127.0.0.1"
87+
[debug] 32488#0: *1 http script set $addr
88+
[debug] 32488#0: *1 realip: "1.2.3.4"
89+
[debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F
90+
[debug] 32488#0: *1 http script var: "127.0.0.1"
91+
92+
Among the logs, the first line writes:
93+
94+
:text
95+
[debug] 32488#0: *1 http script var: "127.0.0.1"
96+
97+
The log is generated when variable L<ngx_core/$remote_addr>
98+
is fetched by command L<ngx_rewrite/set>, string C<"127.0.0.1">
99+
is the fetched value.
100+
101+
The second line writes:
102+
103+
:text
104+
[debug] 32488#0: *1 http script set $addr
105+
106+
It indicates Nginx assigns value to variable C<$addr>.
107+
108+
For the following two lines:
109+
110+
:text
111+
[debug] 32488#0: *1 realip: "1.2.3.4"
112+
[debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F
113+
114+
They are generated when module L<ngx_realip> rewrites
115+
the remote address in C<preaccess> phase. As we can tell,
116+
the new address becomes C<1.2.3.4> as expected but it
117+
happens only after the variable assignment and that's
118+
already too late.
119+
120+
Now the last line:
121+
122+
:text
123+
[debug] 32488#0: *1 http script var: "127.0.0.1"
124+
125+
It is generated when command L<ngx_echo/echo> outputs
126+
variable C<$addr>, clearly the value is the original
127+
remote address, not the rewritten one.
128+
129+
Some people might come up with a solution immediately:"
130+
what if module L<ngx_realip> registers its handlers in
131+
C<rewrite> phase instead, not in C<preacccess> phase ?"
132+
The solution however is, not necessarily correct. This is
133+
because module L<ngx_rewrite> registers its handlers in
134+
C<rewrite> phase too, and we have learnt in L<ordertut/(02)>
135+
that the execution order, under the circumstances, can
136+
not be guaranteed, so there is a good chance that
137+
module L<ngx_realip> still executes its commands after
138+
command L<ngx_rewrite/set>.
139+
140+
Always we have the backup option: instead of C<preaccess>,
141+
try use L<ngx_realip> module in C<server> directive, it
142+
bypasses the bothersome situations encountered above.
143+
144+
After phase C<preaccess>, it is another old friend, the
145+
C<access> phase. As we've learnt, built-in module L<ngx_access>,
146+
3rd party module L<ngx_auth_request> and 3rd party module
147+
L<ngx_lua> (L<ngx_lua/access_by_lua>) have their commands
148+
executed in this phase.
149+
150+
After phase C<access>, it is the C<post-access> phase. Again
151+
as the name implies, we can easily spot that the phase is
152+
executed right after C<access> phase. Similar to C<post-rewrite>,
153+
the phase does not allow Nginx module to register their
154+
handlers, instead it runs a few tasks by Nginx core, among
155+
them, primarily is the L<ngx_core/satisfy> functionality, provided
156+
by module L<ngx_http_core>.
157+
158+
When multiple Nginx module execute their commands in C<access>
159+
phase, command L<ngx_core/satisfy> controls their relationships
160+
in between. For example, both module A and module B register
161+
their access control handlers in C<access> phase, we may have
162+
two working modes, one is to let access when both A and B pass
163+
their control, the other is to let access when either A or B
164+
pass their control. The first one is called C<all> mode ("AND"
165+
relation), the second one is called C<any> mode ("OR" relation)
166+
By default, Nginx uses C<all> mode, below is an example:
167+
168+
:nginx
169+
location /test {
170+
satisfy all;
171+
172+
deny all;
173+
access_by_lua 'ngx.exit(ngx.OK)';
174+
175+
echo something important;
176+
}
177+
178+
Under C</test> directive, both L<ngx_access> and
179+
L<ngx_lua> are used, so we have two modules monitoring
180+
access in C<access> phase. Specifically, statement
181+
C<deny all> tells module L<ngx_access> to rejects all
182+
access, whereas statement C<access_by_lua 'ngx.exit(ngx.OK)'>
183+
allows all access. When C<all> mode is used with command
184+
L<ngx_core/satisfy>, it means to let access only if every
185+
module allows access. Since module L<ngx_access> always
186+
rejects in our case, the request is rejected:
187+
188+
:bash
189+
$ curl localhost:8080/test
190+
<html>
191+
<head><title>403 Forbidden</title></head>
192+
<body bgcolor="white">
193+
<center><h1>403 Forbidden</h1></center>
194+
<hr><center>nginx</center>
195+
</body>
196+
</html>
197+
198+
Careful readers might find following error log in the Nginx
199+
error log file:
200+
201+
:text
202+
[error] 6549#0: *1 access forbidden by rule
203+
204+
If however, we change the C<satisfy all> statement to C<satisfy
205+
any>.
206+
207+
:nginx
208+
location /test {
209+
satisfy any;
210+
211+
deny all;
212+
access_by_lua 'ngx.exit(ngx.OK)';
213+
214+
echo something important;
215+
}
216+
217+
The outcome is completely different:
218+
219+
:bash
220+
$ curl localhost:8080/test
221+
something important
222+
223+
The request is allowed to access. Because overall
224+
access is allowed whenever one module passes the control
225+
in C<any> mode. In our example, module L<ngx_lua>
226+
and its command L<ngx_lua/access_by_lua> always allow
227+
the access.
228+
229+
Certainly, if every module rejects the access in the
230+
C<satisfy any> circumstances, the request will be rejected:
231+
232+
:nginx
233+
location /test {
234+
satisfy any;
235+
236+
deny all;
237+
access_by_lua 'ngx.exit(ngx.HTTP_FORBIDDEN)';
238+
239+
echo something important;
240+
}
241+
242+
Now request to C</test> will encounter C<403 Forbidden>
243+
error page. In the process, the "OR" relation of access
244+
control of each C<access> module, is implemented in
245+
C<post-access>.
246+
247+
Be careful though, above example requires the version of
248+
L<ngx_lua> be 0.5.0rc19 or above, its predecessors cannot
249+
work together with C<satisfy any> statement.

0 commit comments

Comments
 (0)