Celebrazio Net



Contact Us

Nginx for reverse proxying and authentication for backends - Part 2

June, 2020

This is Part 2 - the nitty-gritty details. It was a challenge to identify a solution for enabling this architecture: unsecured backends (think node.js) behind a feature-rich nginx reverse-proxy gateway. The gateway handles SSL termination (TLS really), websockets proxying, and authentication. The backends themselves don't implement authentication, though they do need some authorization control (MongoDB for example, or configure Auth0 to provide it as well - not included in this guide). The auth_request service used is oauth2_proxy in this implementation.

Main Topics Covered in Part 2:

The source for oauth2-proxy code and docs is here: https://oauth2-proxy.github.io/oauth2-proxy/installation

Config. of oauth2-proxy

The provider="oidc" will work best for Auth0, and can leverage auth0 integration with google, etc. It's impressive how many sign-on providers they are integrated with.

I played around with the settings a bit. Apparently many of the settings work with "proxy" but not "auth request" mode, and vice versa.

In the example below the "skip_provider_button" option is commented out, but after testing it, it was an improvement so I set it to "true".


## Oauth2 Proxy config file 
## 
##  https://github.com/oauth2-proxy/oauth2-proxy

## <addr>:<port> to listen on for HTTP/HTTPS clients
# http_address = "127.0.0.1:4180"
http_address = "127.0.0.1:8181"
# https_address = ":443"

## Are we running behind a reverse proxy? Will not accept headers like X-Real-Ip unless this is set.
reverse_proxy = true

#proxy_prefix = "/oauth2" 

## for nginx auth use case: 
pass_auth_basic = true
pass_user_header = true
pass_host_header = true
set_basic_auth  = true 
set_authorization_header = true 
pass_authorization_header = true
set_xauthrequest  = true
real_client_ip_headers = true 
prompt = "login"
login_url = "https://test.nnnnn.com/log_in.html"
footer = "-"

## TLS Settings
# tls_cert_file = ""
# tls_key_file = ""


## the OAuth Redirect URL.
# defaults to the "https://" + requested host header + "/oauth2/callback"
# redirect_url = "https://internalapp.yourcompany.com/oauth2/callback"
redirect_url = "https://test.nnnnn.com/oauth2/callback"

## Email Domains to allow authentication for (this authorizes any email on this domain)
## for more granular authorization use `authenticated_emails_file`
## To authorize any email addresses use "*"
# email_domains = [
#     "yourcompany.com"
# ]
email_domains = [
    "*"
]

## The OAuth Client ID, Secret
# client_secret = ""
client_id = "YObG8qESlt6GH5knw3W...............A"
client_secret = "Jt2BAZI6VhbRM-8Jc603H72.................................."

##    OIDC via Oauth setting: 
provider = "oidc"
oidc_issuer_url = "https://nnnnn.auth0.com/" 
insecure_oidc_allow_unverified_email = true

# skip_provider_button = true 
pass_access_token = true 

## Cookie Settings
## Name     - the cookie name
## Secret   - the seed string for secure cookies; should be 16, 24, or 32 bytes
##            for use with an AES cipher when cookie_refresh or pass_access_token
##            is set
## Domain   - (optional) cookie domain to force cookies to (ie: .yourcompany.com)
## Expire   - (duration) expire timeframe for cookie
## Refresh  - (duration) refresh the cookie when duration has elapsed after cookie was initially set.
##            Should be less than cookie_expire; set to 0 to disable.
##            On refresh, OAuth token is re-validated.
##            (ie: 1h means tokens are refreshed on request 1hr+ after it was set)
## Secure   - secure cookies are only sent by the browser of a HTTPS connection (recommended)
## HttpOnly - httponly cookies are not readable by javascript (recommended)
#cookie_name = "_oauth2_proxy"
cookie_secret = "XeCa7orQZ-Z6tQ1fqxNg==" # not very secret if you copy this one... (not in use)
# cookie_domains = ""
cookie_domain = "nnnnn.com"
# cookie_expire = "168h"
# cookie_refresh = ""
# cookie_secure = true
cookie_secure = false
cookie_httponly = true 
#session_store_type = "cookie"

###
# redirect domain white list: 
whitelist_domains = [
    "nnnnn.com",
    "localhost",
    "test.nnnnn.com",
    "nnnnn.auth0.com"
]

##

## set up logging here  
auth_logging = true
logging_filename = "/var/log/oauth2/oauth2_combined.log"
logging_compress = true
logging_max_backups = 0
logging_local_time = false 
logging_max_size  = 40
logging_max_age  = 400


Optional SystemD Unit

Suggestion: make a systemD Unit from your oauth2_proxy service: A file like this can be set in /etc/systemd/system/oauth2_proxy.service and you can let systemd keep the service always on.

 
[Unit]
Description=oauth2_proxy daemon
After=syslog.target network.target

[Service]
ExecStart=/var/opt/go/bin/oauth2_proxy -config /etc/oauth2_proxy.conf
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target

/etc/systemd/system/oauth2_proxy.service  

Nginx server test-app.conf

The path /oauth2/oauth2/auth is redundant since nginx only passes beginning with the 2nd slash, and oauth2_proxy expects the endpoint "/oauth2/auth" as shown on their list of endpoints.

While this is not our final production config, it is the one that completed the Auth0 proof of concept successfully, including secure websockets and SSL termination. nginx.conf and other snippets not shown here.

   
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
        # see bottom  listen 80 default_server;
        # see bottom  listen [::]:80 default_server;

        # SSL configuration
        #
        listen 443 ssl http2 default_server;
        listen [::]:443 ssl http2 default_server;
        #
        #
        #  certs  
        include snippets/sa-ssl.conf ; 
        include snippets/sa-letsencrypt.conf ; 
        include snippets/sa-general.conf ; 

        server_name test.nnnnn.com ;
        root /var/www/main/htdocs;

        # Add index.php to the list if you are using PHP
        index index.html index.htm ;

        ## SSL / TLS 
        ssl_certificate /etc/letsencrypt/live/test.nnnnn.com/fullchain.pem; 
        ssl_certificate_key /etc/letsencrypt/live/test.nnnnn.com/privkey.pem; 
        ssl_trusted_certificate /etc/letsencrypt/live/test.nnnnn.com/chain.pem; 

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
                autoindex off; 
                limit_except GET HEAD POST { 
                  deny all; 
                }
        }
        
        # Transport Security 
        more_set_headers "Strict-Transport-Security: max-age=27200";  # requires headers_more module 

        # favicon - return 204 / no content / if doesn't exist. 
        location = /favicon.ico {
          try_files /favicon.ico  =204;
          expires 6d; 
          add_header cache-control public ; 
        }
       
        # config the  proxy oauth realm 
        location /oauth2/ {
            proxy_pass       http://oauth2/oauth2/;
            error_page 401  /log_in.html;
            proxy_set_header Host                    $host;
            proxy_set_header X-Real-IP               $remote_addr;
            proxy_set_header X-Scheme                $scheme;
            proxy_set_header X-Auth-Request-Redirect $request_uri;
        }
  
        location = /oauth2/auth {
            #internal; 
            proxy_pass       http://oauth2/oauth2/auth ;
            proxy_set_header Host             $host;
            proxy_set_header X-Real-IP        $remote_addr;
            proxy_set_header X-Origin-URI     $request_uri; 
          #proxy_set_header X-Forwarded-Proto $scheme ;
        } 
        
       location /webapp/ { 
          auth_request /oauth2/auth;
          error_page 401  /log_in.html;
          # include the special headers
          auth_request_set $user   $upstream_http_x_auth_request_user;
          auth_request_set $email  $upstream_http_x_auth_request_email;
          auth_request_set  $sid   $upstream_http_x_session ;   
          proxy_set_header X-User  $user;
          proxy_set_header X-Email $email;
          proxy_set_header X-Remote-SID  $sid; 
          auth_request_set $auth_cookie $upstream_http_set_cookie;
          add_header Set-Cookie $auth_cookie;
          auth_request_set $token  $upstream_http_x_auth_request_access_token;
          proxy_set_header X-Access-Token $token;   
          
          proxy_pass http://webapp/ ;  # defined in nginx.conf
          proxy_set_header Host $host ;
          proxy_set_header X-Real-IP $remote_addr ;
          proxy_http_version 1.1 ;    # back ends are not using SSL anyway...
          proxy_buffering off; 
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
          proxy_set_header X-Forwarded-Proto $scheme ;

        }
        location /wsapp/ { 
          # this is something using websockets.  JS sets it up using wss://  scheme to connect.  
          auth_request /oauth2/auth;
          error_page 401  /log_in.html;
          # include the special headers
          auth_request_set $user   $upstream_http_x_auth_request_user;
          auth_request_set $email  $upstream_http_x_auth_request_email;
          auth_request_set  $sid   $upstream_http_x_session ;   
          proxy_set_header X-User  $user;
          proxy_set_header X-Email $email;
          proxy_set_header X-Remote-SID  $sid; 
          auth_request_set $auth_cookie $upstream_http_set_cookie;
          add_header Set-Cookie $auth_cookie;
          auth_request_set $token  $upstream_http_x_auth_request_access_token;
          proxy_set_header X-Access-Token $token;   
    
          proxy_pass http://wsbase ; 
          proxy_set_header Upgrade $http_upgrade ;
          proxy_set_header Connection $connection_upgrade ; 
          proxy_cache_bypass $http_upgrade ; 
          proxy_buffering off; 
          proxy_set_header Host $host ;
          proxy_http_version 1.1 ; 
        } 
        location /cb/  {
          auth_request /oauth2/auth;
          error_page 401  /log_in.html;

          # pass information via X-User and X-Email headers to backend,
          # requires running with set_xauthrequest =true 
          auth_request_set $user   $upstream_http_x_auth_request_user;
          auth_request_set $email  $upstream_http_x_auth_request_email;
          auth_request_set  $sid   $upstream_http_x_session ;   
          proxy_set_header X-User  $user;
          proxy_set_header X-Email $email;
          proxy_set_header X-Remote-SID  $sid; 
    
          auth_request_set $auth_cookie $upstream_http_set_cookie;
          add_header Set-Cookie $auth_cookie;
    
          # if you enabled --pass-access-token, this will pass the token to the backend
          auth_request_set $token  $upstream_http_x_auth_request_access_token;
          proxy_set_header X-Access-Token $token;   
          
        }
}


# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
server {
        listen 80;
        listen [::]:80;

        server_name test.nnnnn.com ;
        location / {
             return 301 https://test.nnnnn.com;
             limit_except GET HEAD POST { 
                 deny all; 
             }
        }

        location /admin/301_doc.html {

                root /var/www/main/htdocs;
                limit_except GET HEAD POST { 
                    deny all; 
                }
        }
}

Confirmation, Result

From your login page, make a link to:
/oauth2/sign_in?rd=%2Fwebapp%2F
This is how the sign in process begins on this site. (the &rd= value creates a redirect, automatically sending you there upon successful authentication).

Elsewhere, from the secure realm, make a logout link to :
/oauth2/sign_out?rd=%2Findex.html
which, when reached, will remove the oauth2_proxy cookie, signing the user out locally, and redirect to the /index.html url appended (in url-escaped form).

RESULT:
The auth request / response contains only headers, no body. So any useful data should be passed as headers as done in the examples above. These are the headers being passed to the backend after the auth is established on each request:
Headers:
"x-user":"auth0|5ee07e4a4c22coz703d56c3f"
"x-email":"name1@nnnnn.com"
"x-access-token":"dei7LdDPhDEv_JCvsyhgEPuV_h7GMtX"
"host":"test.nnnnn.com"
"x-real-ip":"240f:8:8a:202:7030:d3b4:bf6:3c1f"
"x-forwarded-for":"240f:8:8a:202:7030:d3b4:bf6:3c1f"
"x-forwarded-proto":"https"
"authorization":"Bearer eyJhbmtpZCl6ljJtNW.........FOYf1Flde7qIQ"
"connection":"close"
"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0"
"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
"accept-language":"en-US,en;q=0.5"
"accept-encoding":"gzip, deflate, br"
"referer":"https://test.nnnnn.com/index.html"
"cache-control":"no-cache"
"cookie":"_oauth2_proxy=eyJBY2Nlc3NUb2tlbiI6IkRzR093ekV1TTlXY..............GlCUSW1jWGt3L29I dHV0RXJWd0lRMWxIeHVqemhQZ1ZjYVlINEdiNk0wUVNKRC9Dd0Z1SGZudm1za1JXUT09IiwiQ3JlYXRlZEF0IjoiMjAyMC0wNi0yNF QwNjowODo1MC44ODQwOTAxNloiLCJFeHBpcmVzT24iOiIyMDIwLTA2LTI1VDA2OjA4OjUwLjc3MzUxNTE2OVoifQ==|1592978930|ibLFRJAXM6lv2FIejZvDOJzcl9o="





1998-2020 Celebrazio.net