3

I have several document root directories, /opt/lampp/htdocs/proj1, /opt/lampp/proj2, etc, that have AuthType Basic password protection. I want their uploads sub-directory trees not to require a password. Except that the private sub-directory tree of uploads should not allow any access at all. According to http://httpd.apache.org/docs/current/mod/core.html#directory :

Regular expressions are not considered until after all of the normal sections have been applied. Then all of the regular expressions are tested in the order they appeared in the configuration file.

So I used these directives:

#==========================================================
# regexp enables access to images without login (needed for wp_sideload_image)
<Directory ~ "/opt/lampp/htdocs/[^/]*/public/(uploads|wp/wp-content/uploads)">
    Require all granted
</Directory>

#==========================================================
# regexp protects private uploads unconditionally
# regexps processed in source order, so MUST BE AFTER ENABLING IMAGES
<Directory ~ "/opt/lampp/htdocs/[^/]*/public/uploads/private">
    Require all denied
</Directory>

What I found was that the Require all denied directive was not effective, everything below uploads could be accessed without a password, including things below private. Just for fun I tried removing the alternation expression, making the first block:

<Directory ~ "/opt/lampp/htdocs/[^/]*/public/uploads">
    Require all granted
</Directory>

And much to my surprise (and delight), things worked as I wanted. So I can get the effect of the original regular expression by adding another <Directory>:

<Directory ~ "/opt/lampp/htdocs/[^/]*/public/wp/wp-content/uploads">
    Require all granted
</Directory>

While that does work, I might as well use normal <Directory> blocks with a '*' wildcard for the level below htdocs. I thought it would be more concise to use the regexp form. I still want to use regexp for the private section, though, since I need that to be processed after the directives that grant access.

Can anyone explain why the original code doesn't work? The Apache docs say they use PCRE, and the expression using alternation works fine with egrep. Plus I don't see how that expression could affect the subsequent Require all denied, since they are both regexps, which are supposed to be processed in source order. So the denied directive should override no matter what, shouldn't it? That's what happens when I replace the alternation with two separate regexp directives. What am I missing?

EDIT: At the time I posted this, I was obviously having a brain fart moment thinking that the non-regexp rule of "shortest path first" meant that I needed to use a regexp to ensure that the "Require all denied" would be processed late enough. But clearly, while there could be longer paths below "uploads" than those below "uploads/private", in those cases there wouldn't be a match on "uploads/private", so those paths shouldn't be forbidden anyway. Thus I believe the "best" solution for my actual situation is simply to eliminate the use of "regexp" matching entirely and use three normal "<Directory>" directives with wildcards similar to the ones shown with regexps above, but replacing the "[^/]*" regexp with the shell wildcard "*". That works just fine, and likely is microseconds faster :-)

However, I'd still like to know why my original code did not work. It still seems to me that Apache's treatment of the directive containing the "|" regexp operator was incorrect.

2 Answers 2

1

That question made me mix quite some things. This one works as expected according to what I tested, with | operator, taking advantage of the possibility in Apache 2.4 to use the $ sign as line ending:

<DirectoryMatch "/opt/lampp/htdocs/[^/]*/public/(uploads(/[^/]*)?|(wp/wp-content/uploads(/[^/]*)?))$">
    Require all granted
</DirectoryMatch>
<DirectoryMatch "/opt/lampp/htdocs/[^/]*/public/uploads/private">
    Require all denied
</DirectoryMatch>

I thought I understood partly why this behavior, but in fact I didn't understand it. I guess this is something I'll ask to the Apache mailing list.

5
  • Your example seems to be talking about a single Directory section containing both "Require all granted" and "Require all denied". I agree that in that case the order of the Require directives does not matter. But in my example, there were separate directory sections using regexp matching, and the documentation as I quoted it says that they are tested in source order. Separate non-regexp directory directives are applied shortest-match-first. You normally "deny all" on "/" and grant permissions on deeper directories. Deeper comes later and since deeper is longer, the grant overrides the deny.
    – sootsnoot
    Jan 19, 2015 at 21:09
  • Note that simply changing the regexp using alternation to two separate Directory sections with regexps not using the alternation operator causes it to work: the subsequent regexp Directory section with the "Require all denied" does in fact override the previous matching section with "Require all granted". Your comment about regexp not matching subfolders also is contradicted by the fact that changing the alternation to two separate regexp matches works correctly (denying access to files in subfolders below private).
    – sootsnoot
    Jan 19, 2015 at 21:18
  • @sootsnoot You're perfectly right I messed up these two Directories in only one. So I think I should delete this answer, as it didn't help at all. May be just leave the part about "our logic is broken" and change it to "my logic is broken" :-) But the regex that I proposed, and that have a alternation operator in it work as expected, I just retested it on a testing/learning apache 2.4. Did you try it? It stays a mystery for me why your original setup doesn't work as expected.
    – Zimmi
    Jan 19, 2015 at 23:43
  • No, the regexp you have now is nothing like the one in your original answer, so I hadn't seen it. If you say this works, I'll believe you. But I see no rationale for why such a regexp should work when the one in the original question did not. And as I pointed out in my comment on Stephen Ostermiller's answer, even if the regular expression with alternation matched when it shouldn't or didn't match when it should, the behavior was that the final block with the Denied directive was not triggering for paths below uploads/private.
    – sootsnoot
    Jan 21, 2015 at 1:55
  • Replacing the Directory block using the regexp with alternation by a sequence of two Direcctory blocks, each testing a regexp making up one of the two alternatives, and changing nothing else, allowed the Denied directive to trigger for paths below uploads/private. So in my mind, the use of the alternation operator that I originally had triggers a behavior bug other than incorrect regexp matching.
    – sootsnoot
    Jan 21, 2015 at 2:04
0

My only suggestion would be try adding more parenthesis to the regular expression:

<Directory ~ "/opt/lampp/htdocs/[^/]*/public/(uploads|(wp/wp-content/uploads))">

The order of operations with | and / could be part of the issue.

1
  • Well, "/" is not a PCRE operator... And the "interesting" thing is that even if the regexp with the "|" wasn't matching as expected, the final "Require all denied" is the last of the regexp Directory blocks, and so it should be applied last and "win". But when the "|" is present on the block containing the grant directive, the "denied" directive doesn't "win". To me that makes absolutely no sense!!
    – sootsnoot
    Jan 19, 2015 at 21:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.