7. Say it with style
IndexStyleSheet /styles/dir.css
...
.odd {
background-color: #eef;
}
.even {
background-color:
#fff;
}
8. Caveat: Some features 2.4 only
• In 2.2 and earlier, you can specify
a style sheet, but no classes are
added to the HTML
• Useful, but not quite as useful
9. Now, with extra class
<table id="indexlist">
<tr class="indexhead">
<th class="indexcolicon">...
<th class="indexcolname">...
<th class="indexcollastmod">...
<th class="indexcolsize">...
<th class="indexcoldesc">...
<tr class="indexbreakrow">...
<tr class="even"><td class="indexcolicon">...
10. An exercise for the reader:
• Nifty mouse-over effects
(JS in HeaderName
file?)
Photo CC by Ugglan - Flickr
• AJAXy file interaction
of some kind?
• Photo gallery, entirely
based on
mod_autoindex and
Javascript?
11. mod_substitite
• New module in 2.2
• Rewrite content using
regular expressions
• Syntax is identical to sed
13. Proxying
• More useful example
• Proxying to back-end server that returns
fully-qualified URLs
LoadModule substitute_module
libexec/apache2/mod_substitute.so
AddOutputFilterByType SUBSTITUTE text/html
Substitute s/backend.local/www.example.com/ni
19. Make sure you ...
# Inspect POST payloads
SecFilterScanPOST On
# Default action set
SecFilterDefaultAction "deny,log,status:406"
19
20. While you’re at it
# Inspect POST payloads
SecFilterScanPOST On
SecFilter “vidocin”
20
21. Acceptable arguments
# Only for the FormMail script
<Location /cgi-bin/FormMail>
# Reject request where the value of parameter "recipient"
# does not end with "@apache.org"
SecFilterSelective ARG_recipient
"![a-zA-Z0-9]+@apache.org$">
</Location>
21
22. Which PHP script is slagging my server?
• Which process, and what is
it doing?
• Look at /server-status for a
process list
23. Step 1: Look at top:
• Run ‘top’
• Order by CPU usage
• Pick off the httpd processes
that are causing the
problem and make note of
their PIDs
24. Step 2: /server-status
• Look at /server-status
output
• Look for the PIDs you
noted
• Move fast - the process
may be gone already
26. ExtendedStatus On
• You’ll need “ExtendedStatus On”
to get these details:
<Location /server-status>
SetHandler server-status
</Location>
ExtendedStatus On
27. SNI
• General knowledge: SSL
requires one certificate per IP
address
• That is, only one SSL site can
be on each IP address
• Limitation of SSL itself
32. SNI
• Server Name Indication
• Passes server name in initial
handshake
• Simple solutions are always
best
33. Caveats
• You knew there would be a catch
Mozilla Firefox 2.0 or later
Opera 8.0 or later (the TLS 1.1 protocol must be
enabled)
Internet Explorer 7 (Vista or higher, not XP) or
later
Google Chrome (Vista or higher, not XP. OS X
10.5.7 or higher on Chrome 5.0.342.1 or newer)
Safari Safari 3.2.1 and newer on Mac OS X 10.5.6
and Windows Vista or higher, not XP
34. Apache • 2.2.12 or later
Listen 443
# Listen for virtual host requests on all IP addresses
NameVirtualHost *:443
# Go ahead and accept connections for these vhosts
# from non-SNI clients
SSLStrictSNIVHostCheck off
<VirtualHost *:443>
DocumentRoot /www/example1
ServerName www.example.com
</VirtualHost>
<VirtualHost *:443>
DocumentRoot /www/example2
ServerName www.example2.org
</VirtualHost>
36. Secure mod_dav deployment
• Security rule #1: Content is not
writable
• Corollary: Anyone telling you to
‘chmod 777’ is a monkey
• Anyone telling you ‘chown apache
something.php’ *might* be a
monkey. Or they might be working
around Apache’s annoying
permissions model
37. However ...
•Setting up WebDAV
requires that content be
writable ...
•And owned by Apache
•This is annoying
38. WebDAV
<Directory /var/www/content>
Dav On
</Directory>
39. Why this is a problem
• People write bad code
• It’s easy to get PHP (or whatever
else) to overwrite your content
• Now your site is a radical terrorist
site
• This is a very unpleasant thing to
wake up to on a Saturday morning
40. Secure DAV
• It’s possible to set up Dav without
having the files written by the
Apache user
• Sort of
40
41. Two Apache Processes
Primary Secondary
server, server,
running as running as
user www user dav
File System,
owned by dav
41
42. Two Apache Processes
Read-only
Running ordinary set of modules
Running web apps, etc
Primary
server,
running as
user www
File System,
owned by dav
42
43. Two Apache Processes
Read/Write
Remove all extra modules
SSL Secondary
server,
Authenticated
running as
user dav
File System,
owned by dav
43
44. Multi-server config.
drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www
1
Listen *:80
User www
Group www
DocumentRoot /var/www/
Listen *:8080 2
DavLockDb /var/lock/dav
User dav
Group dav
DocumentRoot /var/www/
44
45. drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www
1
Listen *:80 Can write
User www
Group www
DocumentRoot /var/www/
Listen *:8080 2
DavLockDb /var/lock/dav
User dav
Group dav
DocumentRoot /var/www/
45
46. drwxr-xr-x 2 dav dav 68 Oct 3 12:41 /var/www
1 Can’t
Listen *:80
User www
Group www
DocumentRoot /var/www/
Listen *:8080 2
DavLockDb /var/lock/dav
User dav
Group dav
DocumentRoot /var/www/
46
47. <If> Picture by BrewBooks (Flickr)
My favorite new feature in 2.4
49. <If>
<If ‘$req{Host} = “www.example.com”’>
RedirectMatch (.*) http://example.com/$1
</If>
This was hard prior to 2.4, and
probably required mod_rewrite,
or a separate virtual host.
52. Conditional LogFormat
• The LogFormat directive
supports some conditionals in
the variables
• "%!200,304,302{Referer}i"
logs Referer on all requests
that do not return one of the
three specified codes
52
53. Conditional LogFormat
• "%400,501{User-agent}i" logs
User-agent on 400 errors and
501 errors only
• For other status codes, the
literal string "-" will be logged
53
54. Conditional CustomLog
• Stick Env=xyz on the end
• or Env=!xyz
• Yes, that’s =! not !=
54
55. For example ...
SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-requests.log common env=!gif-image
55
56. For example ...
SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-requests.log common env=!gif-image
56
57. For example ...
SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-requests.log common env=!gif-image
57
58. So the images get logged here
SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-requests.log common env=!gif-image
58
59. And everything else goes here
SetEnvIf Request_URI .gif$ gif-image
CustomLog gif-requests.log common env=gif-image
CustomLog nongif-requests.log common env=!gif-image
59
61. PCRE Zero-width assertions
• Match everything except one thing
• While you can do this with
RewriteRule, it would be nice if you
could do it with other directives
DirectoryMatch
FilesMatch
RedirectMatch
62. Negative Lookahead
• PCRE provides a regex
syntax to say “not preceded
by” or “not followed by”
• Negative lookbehind and
negative lookahead,
respectively
63. Andrei Rocks
• While we’re on the topic:
• Pretty much the best
presentation on regular
expressions anywhere:
• http://www.slideshare.net/
andreizm/andreis-regex-clinic
64. Everything except ...
• “I want to redirect everything except /
images”
• This is where you’d use a negative lookahead
• Necessary because RedirectMatch doesn’t
support negation
RedirectMatch ^/(?!images/)(.*)
http://other.myhost.com/$1
66. Everything except ...
• Match anything ...
• That doesn’t start with images/
RedirectMatch ^/(?!images/)(.*)
http://other.myhost.com/$1
67. Everything except ...
This is called a “zero width” assertion,
because it doesn’t fill $1, and doesn’t
consume any characters in the match.
RedirectMatch ^/(?!images/)(.*)
http://other.myhost.com/$1
68.
69. What the heck is it doing?
RewriteLog /var/log/rewrite.log
RewriteLogLevel 9
70. What the heck is it doing?
RewriteLog /var/log/rewrite.log
RewriteLogLevel 9
• Alas, not in .htaccess
• Logs are always opened at startup
71. What the heck is it doing?
RewriteLog /var/log/rewrite.log
RewriteLogLevel 9
• Most entries between 1-4
72. What the heck is it doing?
RewriteLog /var/log/rewrite.log
RewriteLogLevel 9
• If there’s nothing in there, your rules are
being ignored.
88. Piped logs
• I actually use a piped lot handler to
remove this superfluous stuff
• Like so ...
RewriteLog |/usr/local/bin/rewrite_log_pipe
RewriteLogLevel 9
88
89. RewriteLog |/usr/local/bin/rewrite_log_pipe
RewriteLogLevel 9
with ...
#!/usr/bin/perl
$|++;
open (F, ">>/tmp/rewrite");
select F;
while (<>) {
s/^.*((d).*)/$1/;
print;
}
89
91. #!/usr/bin/perl
$|++;
open (F, ">>/tmp/rewrite");
select F;
while (<>) {
s/^.*((d).*)/$1/;
print;
}
• Look for the (1) or (2) bit
• drop everything before that
91
92. Results in:
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) applying pattern 'wp-rss2.php' to uri '/index.php'
(3) applying pattern '(journal/)?index.rdf' to uri '/index.php'
(3) applying pattern '^/wordpress/wp-comments' to uri '/index.php'
(3) applying pattern '^/perm/(.*)' to uri '/index.php'
(3) applying pattern '^/articles?/(.*)' to uri '/index.php'
(3) applying pattern '^/blog/(.*)' to uri '/index.php'
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php'
(3) applying pattern '^/book/cookbook' to uri '/index.php'
(3) applying pattern '^/book/2.2' to uri '/index.php'
(3) applying pattern '^/booklink/(.*)' to uri '/index.php'
(3) applying pattern '^/books?/(.+)' to uri '/index.php'
(1) pass through /index.php
92
93. Results in:
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) applying pattern 'wp-rss2.php' to uri '/index.php'
(3) applying pattern '(journal/)?index.rdf' to uri '/index.php'
(3) applying pattern '^/wordpress/wp-comments' to uri '/index.php'
(3) applying pattern '^/perm/(.*)' to uri '/index.php'
(3) applying pattern '^/articles?/(.*)' to uri '/index.php'
(3) applying pattern '^/blog/(.*)' to uri '/index.php' better?
See? Isn’t that
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php'
(3) applying pattern '^/book/cookbook' to uri '/index.php'
(3) applying pattern '^/book/2.2' to uri '/index.php'
(3) applying pattern '^/booklink/(.*)' to uri '/index.php'
(3) applying pattern '^/books?/(.+)' to uri '/index.php'
(1) pass through /index.php
93
94. Requested URI
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) applying pattern 'wp-rss2.php' to uri '/index.php'
(3) applying pattern '(journal/)?index.rdf' to uri '/index.php'
(3) applying pattern '^/wordpress/wp-comments' to uri '/index.php'
(3) applying pattern '^/perm/(.*)' to uri '/index.php'
(3) applying pattern '^/articles?/(.*)' to uri '/index.php'
(3) applying pattern '^/blog/(.*)' to uri '/index.php'
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php'
(3) applying pattern '^/book/cookbook' to uri '/index.php'
(3) applying pattern '^/book/2.2' to uri '/index.php'
(3) applying pattern '^/booklink/(.*)' to uri '/index.php'
(3) applying pattern '^/books?/(.+)' to uri '/index.php'
(1) pass through /index.php
94
95. Patterns applied
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) applying pattern 'wp-rss2.php' to uri '/index.php'
(3) applying pattern '(journal/)?index.rdf' to uri '/index.php'
(3) applying pattern '^/wordpress/wp-comments' to uri '/index.php'
(3) applying pattern '^/perm/(.*)' to uri '/index.php'
(3) applying pattern '^/articles?/(.*)' to uri '/index.php'
(3) applying pattern '^/blog/(.*)' to uri '/index.php'
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php'
(3) applying pattern '^/book/cookbook' to uri '/index.php'
(3) applying pattern '^/book/2.2' to uri '/index.php'
(3) applying pattern '^/booklink/(.*)' to uri '/index.php'
(3) applying pattern '^/books?/(.+)' to uri '/index.php'
(1) pass through /index.php
95
96. None of them matched
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
(3) applying pattern 'wp-rss2.php' to uri '/index.php'
(3) applying pattern '(journal/)?index.rdf' to uri '/index.php'
(3) applying pattern '^/wordpress/wp-comments' to uri '/index.php'
(3) applying pattern '^/perm/(.*)' to uri '/index.php'
(3) applying pattern '^/articles?/(.*)' to uri '/index.php'
(3) applying pattern '^/blog/(.*)' to uri '/index.php'
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/index.php'
(3) applying pattern '^/book/cookbook' to uri '/index.php'
(3) applying pattern '^/book/2.2' to uri '/index.php'
(3) applying pattern '^/booklink/(.*)' to uri '/index.php'
(3) applying pattern '^/books?/(.+)' to uri '/index.php'
(1) pass through /index.php
96
97. And now
• We can actually make some
sense of what’s happening
• Less inscrutable noise
• Yes, it means something, but
not to normal people
97
100. Examples
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
• And what pattern was applied
RewriteCond %{HTTP_HOST}
!^wooga.drbacchus.com [NC]
100
101. Examples
(4) RewriteCond: input='wooga.drbacchus.com' pattern='!^wooga.drbacchus
.com' [NC] => not-matched
• As well as what happened
RewriteCond %{HTTP_HOST}
!^wooga.drbacchus.com [NC]
101
102. Another example
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
index.php'
• Was a result of
RewriteRule ^/book/(mod)?_?rewrite
http://www.amazon.com/exec/obidos/asin/
1590595610/drbacchus/ [R,L]
102
103. Again ...
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
index.php'
• What was requested
RewriteRule ^/book/(mod)?_?rewrite
http://www.amazon.com/exec/obidos/asin/
1590595610/drbacchus/ [R,L]
103
104. And ...
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
index.php'
• What it was compared against
RewriteRule ^/book/(mod)?_?rewrite
http://www.amazon.com/exec/obidos/asin/
1590595610/drbacchus/ [R,L]
104
105. Matched?
(3) applying pattern '^/book/(mod)?_?rewrite' to uri '/
index.php'
• If it matched, the next line will be
the action log
RewriteRule ^/book/(mod)?_?rewrite
http://www.amazon.com/exec/obidos/asin/
1590595610/drbacchus/ [R,L]
105
106. The whole thing
(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/
asin/1590595610/drbacchus/'
(2) explicitly forcing redirect with http://www.amazon.com/exec/
obidos/asin/1590595610/drbacchus/
(1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ for redirect
(1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ [REDIRECT/302]
106
107. The match:
(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/
asin/1590595610/drbacchus/'
(2) explicitly forcing redirect with http://www.amazon.com/exec/
obidos/asin/1590595610/drbacchus/
(1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ for redirect
(1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ [REDIRECT/302]
107
108. Followed by
(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/
asin/1590595610/drbacchus/'
(2) explicitly forcing redirect with http://www.amazon.com/exec/
obidos/asin/1590595610/drbacchus/
(1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ for redirect
(1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ [REDIRECT/302]
108
109. [R]
(3) applying pattern '^/books?/(mod)?_?rewrite' to uri '/books/rewrite'
(2) rewrite '/books/rewrite' -> 'http://www.amazon.com/exec/obidos/
asin/1590595610/drbacchus/'
(2) explicitly forcing redirect with http://www.amazon.com/exec/
obidos/asin/1590595610/drbacchus/
(1) escaping http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ for redirect
(1) redirect to http://www.amazon.com/exec/obidos/asin/1590595610/
drbacchus/ [REDIRECT/302]
109
110. But it all runs together!
• Look for:
•
(2) init rewrite engine with requested uri /atom/1
• ‘init rewrite engine’ shows where
a new request started being
rewritten
110
118. Sticky Sessions
Ensures that connections go to the same server they
started with.
ProxyPass / balancer://mycluster/
stickysession=PHPSESSIONID
118
119. Balancing measures
ProxyPass / balancer://hotcluster/
<Proxy balancer://hotcluster>
BalancerMember http://1.2.3.4:8009 loadfactor=1
BalancerMember http://1.2.3.5:8009 loadfactor=2
# The below is the hot standby
BalancerMember http://1.2.3.6:8009 status=+H
ProxySet lbmethod=bytraffic
</Proxy>
119
120. 1.2.3.5 gets twice the traffic
ProxyPass / balancer://hotcluster/
<Proxy balancer://hotcluster>
BalancerMember http://1.2.3.4:8009 loadfactor=1
BalancerMember http://1.2.3.5:8009 loadfactor=2
# The below is the hot standby
BalancerMember http://1.2.3.6:8009 status=+H
ProxySet lbmethod=bytraffic
</Proxy>
120
121. Hot spare
ProxyPass / balancer://hotcluster/
<Proxy balancer://hotcluster>
BalancerMember http://1.2.3.4:8009 loadfactor=1
BalancerMember http://1.2.3.5:8009 loadfactor=2
# The below is the hot standby
BalancerMember http://1.2.3.6:8009 status=+H
ProxySet lbmethod=bytraffic
</Proxy>
121
122. bytraffic or byrequests
ProxyPass / balancer://hotcluster/
<Proxy balancer://hotcluster>
BalancerMember http://1.2.3.4:8009 loadfactor=1
BalancerMember http://1.2.3.5:8009 loadfactor=2
# The below is the hot standby
BalancerMember http://1.2.3.6:8009 status=+H
ProxySet lbmethod=bytraffic
</Proxy>
122
123. BalancerManager
<Location /balancer-manager>
SetHandler balancer-manager
Order Deny,Allow
Deny from all
Allow from .example.com
</Location>
123
139. Less than half the story
• Only bytes transferred to the
client
• Doesn’t include headers
• Doesn’t include data sent from
the client to the server
139
140. mod_logio
•Adds two additional
variables
•%I - Input bytes
•%O - Output bytes
•Includes headers, both
directions
140
145. mod_speling
CheckSpelling On
CheckCaseOnly On
146. Image Theft
SetEnvIf Referer
".example.com/" local_referal
# Allow browsers that do not send Referer info
SetEnvIf Referer "^$" local_referal
<Directory /web/images>
Order Deny,Allow
Deny from all
Allow from env=local_referal
</Directory>
147. Or ...
RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]
RewriteRule .(jpe?g|gif|png)$ - [F,NC]
148. Or ...
RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]
# depending upon in which context
# you use the RewriteRule,
# you might need a condition to
# exclude the go_away.png to prevent
# an internal redirect looping. We don't use a RegEx here:
RewriteCond %{REQUEST_URI} !=/images/go_away.png
RewriteRule .(jpe?g|gif|png)$ /images/go_away.png [NC,L]
149. Or ...
RewriteEngine on
RewriteCond %{HTTP_REFERER} !=""
RewriteCond %{HTTP_REFERER} !example.com [NC]
# depending upon in which context
# you use the RewriteRule,
# you might need a condition to
# exclude the go_away.png to prevent
# an internal redirect looping. We don't use a RegEx here:
RewriteCond %{REQUEST_URI} !=/images/go_away.png
RewriteRule .(jpe?g|gif|png)$
http://other.example.com/images/go_away.png [R,NC,L]
155. Engineering for failure
• Why not do it right to
begin with, and avoid the
mess?
• PHP makes this fairly
easy
156. Step One: SetHandler
/monkeys/lemur/green/99.7/info
• We want monkeys to be a PHP script
• We rename monkeys.php to monkeys, and
then ...
<Files monkeys>
SetHandler application/x-httpd-php
</Files>
This goes in your
server config, or
in .htaccess
157. Step Two: explode()
/monkeys/lemur/green/99.7/info
• The rest of the solution is in your
PHP:
$args = explode( $_SERVER[‘PATH_INFO’] );
$type = $args[0];
$color = $args[1];
...
158. While we’re at it
• File extensions are *so* 1980s
• All your files are php files, right?
• Why give them a .php extension?
RewriteCond %{REQUEST_URI} !.
RewriteRule ^ - [H=application/x-httpd-php,PT]
159. Write a better FM
• I’ll never get this far in the presentation,
right?