Apache 2.4 how to Require valid-user only for specific hosts

Apache 2.4 how to Require valid-user only for specific hosts

I want to tell Apache 2.4.9 to require valid-user if host is dev.example.com or test.example.com. This doesn’t work:
AuthType Basic
AuthName “Speak, friend, and enter.”
AuthBasicProvider file
AuthUserFile /sites/example/conf/.htpasswd
AuthGroupFile /dev/null
SetEnvIfNoCase Host ^dev\.example\.com$ env_is_protected
SetEnvIfNoCase Host ^test\.example\.com$ env_is_protected
Require valid-user
Require not env env_is_protected

It causes a server error; evidently not env is not valid contrary to the documentation.
In the following examples, the first five lines are always the same as in the first example.
This doesn’t work:
SetEnvIfNoCase Host ^dev\.example\.com$ env_is_protected
SetEnvIfNoCase Host ^test\.example\.com$ env_is_protected

Require valid-user

Require env env_is_protected

It causes a server error. The documentation explains:

Because negated authorization directives are unable to return a
successful result, they can not significantly influence the result of
directive. (At most they could cause the directive to
fail in the case where they failed and all other directives returned a
neutral value.) Therefore negated authorization directives are not
permitted within a directive.

This doesn’t work:
SetEnv env_is_unprotected 1
SetEnvIfNoCase Host ^dev\.example\.com$ !env_is_unprotected
SetEnvIfNoCase Host ^test\.example\.com$ !env_is_unprotected
Require valid-user
Require env env_is_unprotected

The documentation explains about SetEnv:

The internal environment variables set by this directive are set after
most early request processing directives are run, such as access
control and URI-to-filename mapping. If the environment variable
you’re setting is meant as input into this early phase of processing
such as the RewriteRule directive, you should instead set the
environment variable with SetEnvIf.

This works:
SetEnvIf Host . env_is_unprotected
SetEnvIfNoCase Host ^dev\.example\.com$ !env_is_unprotected
SetEnvIfNoCase Host ^test\.example\.com$ !env_is_unprotected
Require valid-user
Require env env_is_unprotected

This looks like a hack and takes hours to figure out. Have I failed to discover the proper way of accomplishing my very simple purpose? Is there a better way?

Solutions/Answers:

Solution 1:

This is how I do it.
(Using Cj Case’s nice citation as a comment to explain details. 😀 )

SetEnvIfNoCase HOST ^dev\.example\.com$ env_is_protected
SetEnvIfNoCase HOST ^test\.example\.com$ env_is_protected
<RequireAny>
    <RequireAll>
        # when the Require directive is negated it can only fail or return a neutral result, 
        # and therefore may never independently authorize a request
        # (reason why we need an additional "all granted" here!)
        Require all granted
        Require not env env_is_protected
    </RequireAll>
    AuthType Basic
    AuthName "Speak, friend, and enter."
    AuthUserFile /sites/example/conf/.htpasswd
    Require valid-user
</RequireAny>

Solution 2:

You’re only able to use the “not” directive on a RequireAll block and not as an authentication directive.

The result of the Require directive may be negated through the use of the not option. As with the other negated authorization directive , when the Require directive is negated it can only fail or return a neutral result, and therefore may never independently authorize a request.

http://httpd.apache.org/docs/2.4/mod/mod_authz_core.html#require

This would probably work:

<Directory /your/protected/directory/>
  AuthType Basic
  AuthName "Speak, friend, and enter."
  AuthBasicProvider file
  AuthUserFile /sites/example/conf/.htpasswd
  AuthGroupFile /dev/null

  SetEnvIfNoCase Host ^dev\.example\.com$ env_is_protected
  SetEnvIfNoCase Host ^test\.example\.com$ env_is_protected

  <RequireAll>
    Require valid-user
    Require not env env_is_protected
  </RequireAll>
</Directory>

References