1

I have a service running behind a Apache Reverse-Proxy that uses the custom headers "username" and "role" to identify users and their role.

I want Apache HTTPD to restrict access to to people whose custom HTTP-header "groupmembership" contains one of the following: "viewer","publisher","administrator".

The Apache sits behind another proxy which authenticates users and populates the HTTP Headers "username" and "groupmembership" where the contents of "groupmembership" is a comma-separated list with groups.

For reference I have included a draft of the architecture. http-proxy-auth

How would this be possible? I have tried using a require directive like Require expr %{HTTP:iv_groupmembership} in { 'viewer', 'publisher', 'administrator' } inside <Location /> to no avail.

Could this instead work with mod_rewrite?

Here is the reverse-proxy config using mod_proxy and mod_rewrite:

RewriteEngine on
<Proxy *>
  Allow from all
</Proxy>
ProxyRequests Off

# store variable values with dummy rewrite rules
RewriteRule . - [E=req_scheme:%{REQUEST_SCHEME}]
RewriteRule . - [E=http_host:%{HTTP_HOST}]
RewriteRule . - [E=req_uri:%{REQUEST_URI}]

# set header with variables
RequestHeader set X-RSC-Request "%{req_scheme}e://%{http_host}e%{req_uri}e"

RewriteCond %{HTTP:Upgrade} =websocket
RewriteRule /(.*) ws://localhost:3939/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket
RewriteRule /(.*) http://localhost:3939/$1 [P,L]
ProxyPass / http://172.17.0.1:3939/  
ProxyPassReverse / http://172.17.0.1:3939/

Thanks for any hints.

Edit: Basically, the question boils down to: How can I check if the comma-separated list in the groupmembership Header contains either 'Administrator', 'Publisher' or 'Viewer'

1

This should do it. If you have a comma-separated list in header groupmembership, then use the first regex expression. One value in the list has to match to grant access.

If you want to match an exact value in iv_groupmembership, then uncomment the second third expression (and comment the first).

Edit:

  • added RequestHeader set role example, uncomment as needed. I only tested this with Header set role (in the response, no backend), but should work with RequestHeader the same way.

Edit2:

  • removed <Location/> for clarity
  • added example for X-Auth-Token check combined with groupmembership

Sample config:

    # test for string in comma-separated values, one of the values must match
    <If "%{HTTP:groupmembership} !~ /(^|,)(viewer|publisher|administrator)(,|$)/">

    # combined: X-Auth-Token must be set or one of the roles must exist
#   <If "%{HTTP:X-Auth-Token} == '' && %{HTTP:groupmembership} !~ /(^|,)(viewer|publisher|administrator)(,|$)/">

    # alternative: test for a single value in "iv_groupmembership" (exact match)
#   <If "! %{HTTP:iv_groupmembership} in { 'viewer', 'publisher', 'administrator' }">

        Require all denied
    </If>

    # set role of groupmembership
#   <If "%{HTTP:groupmembership} =~ /(^|,)viewer(,|$)/">
#       RequestHeader set role viewer
#   </If>
#   <ElseIf "%{HTTP:groupmembership} =~ /(^|,)publisher(,|$)/">
#       RequestHeader set role publisher
#   </ElseIf>
#   <ElseIf "%{HTTP:groupmembership} =~ /(^|,)administrator(,|$)/">
#       RequestHeader set role administrator
#   </ElseIf>

    # set role of iv_groupmembership
#   RequestHeader set role "expr=%{HTTP:iv_groupmembership}"

Modifying the headers in Apache (like RequestHeader set iv_groupmembership "viewer") to debug/test the config doesn't work, you need to set the header very early.

https://httpd.apache.org/docs/2.4/expr.html#vars

The expression parser provides a number of variables of the form %{HTTP_HOST}. Note that the value of a variable may depend on the phase of the request processing in which it is evaluated. For example, an expression used in an <If > directive is evaluated before authentication is done. Therefore, %{REMOTE_USER} will not be set in this case.

You can test the configuration with wget from commandline, replace localhost with your hostname.

# HTTP/1.1 403 Forbidden
wget -S -O - http://localhost
wget -S -O - --header='groupmembership: xviewer' http://localhost
wget -S -O - --header='groupmembership: administratorx' http://localhost
wget -S -O - --header='iv_groupmembership: xviewer' http://localhost

# HTTP/1.1 200 OK
wget -S -O - --header='groupmembership: foo,viewer' http://localhost
wget -S -O - --header='groupmembership: publisher,foo' http://localhost
wget -S -O - --header='iv_groupmembership: viewer' http://localhost
wget -S -O - --header='iv_groupmembership: publisher' http://localhost
wget -S -O - --header='iv_groupmembership: administrator' http://localhost

Tested with Apache/2.4.25 (Debian)

  • Thanks for the really helpful answer! – juo Apr 25 at 11:36
  • Now, I actually need both: authorized access and setting a header "role" based on an exact match. – juo Apr 25 at 11:38
  • There is one problem. The RewriteRule [P,L] lines will take precedence on the Location blocks. – user188737 Apr 25 at 12:58
1

Thanks @Freddy, I got it working with the Require and Expressions directives for the authorization part and putting the RequestHeader directive inside <Virtualhost> for setting the Role-Headers.

The final config looks like so:

<VirtualHost *:443>
  ServerName ...
  DocumentRoot /var/www/html

  SSLEngine on
  SSLProtocol all -SSLv2 -SSLv3
  SSLCipherSuite HIGH:3DES:!aNULL:!MD5:!SEED:!IDEA

  SSLCertificateFile /etc/httpd/ssl/...
  SSLCertificateKeyFile /etc/httpd/ssl/...

  RewriteEngine on
  #ProxyPreserveHost on

  <Proxy *>
    Allow from all
  </Proxy>
  ProxyRequests Off

  # store variable values with dummy rewrite rules
  RewriteRule . - [E=req_scheme:%{REQUEST_SCHEME}]
  RewriteRule . - [E=http_host:%{HTTP_HOST}]
  RewriteRule . - [E=req_uri:%{REQUEST_URI}]

  # set header with variables
  RequestHeader set X-RSC-Request "%{req_scheme}e://%{http_host}e%{req_uri}e"

  RewriteCond %{HTTP:Upgrade} =websocket
  RewriteRule /(.*) ws://localhost:3939/$1 [P,L]
  RewriteCond %{HTTP:Upgrade} !=websocket
  RewriteRule /(.*) http://localhost:3939/$1 [P,L]

  # test for string in comma-separated values, one of the values must match
  <If "%{HTTP:iv_groupmembership} !~ /(^|,)(Viewer|Publisher|Administrator)(,|$)/ && -z %{HTTP:X-Auth-Token} && %{HTTP:Authorization} !~ /Key .+/">
    Require all denied
  </If>

  # set role of groupmembership
  <If "%{HTTP:iv_groupmembership} =~ /(^|,)Viewer(,|$)/">
     RequestHeader set Role viewer
  </If>
  <ElseIf "%{HTTP:iv_groupmembership} =~ /(^|,)Publisher(,|$)/">
     RequestHeader set Role publisher
  </ElseIf>
  <ElseIf "%{HTTP:iv_groupmembership} =~ /(^|,)Administrator(,|$)/">
    RequestHeader set Role administrator
  </ElseIf>
</Virtualhost>

Thanks a lot!

Edit: how can I add additional logic in the Require directive, so that if the header X-Auth-Token is present, access is granted anyway?

Edit2: Thanks again @Freddy for the pointer to Apache expression-logic, I adapted the test for existence of the "X-Auth-Token" Header with -z %{HTTP:X-Auth-Token} and could add another condition to pass through requests containing a key inside the "authorization" header.

  • See my Edit2, I also changed the syntax of the negated regex expr from ! %{..} =~ to %{..} !~, just so you know when you adapt the example. – Freddy Apr 25 at 22:21
  • Thanks @Freddy, it's working as expected, now =) – juo Apr 26 at 8:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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