Authorization based on custom Header (Apache)

6,908

Solution 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)

Solution 2

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.

Share:
6,908

Related videos on Youtube

juo
Author by

juo

Updated on September 18, 2022

Comments

  • juo
    juo almost 2 years

    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'

  • juo
    juo about 5 years
    Now, I actually need both: authorized access and setting a header "role" based on an exact match.
  • Gerrit
    Gerrit about 5 years
    There is one problem. The RewriteRule [P,L] lines will take precedence on the Location blocks.
  • Freddy
    Freddy about 5 years
    See my Edit2, I also changed the syntax of the negated regex expr from ! %{..} =~ to %{..} !~, just so you know when you adapt the example.
  • juo
    juo about 5 years
    Thanks @Freddy, it's working as expected, now =)