0

Compiling NGINX with — with-http_auth_request_module on Centos 7

While looking at SSO solutions I decided to investigate a bit more options how I could use Nginx with solutions like Okta to protect my resources. One of interesting ones was using authentication proxy with Nginx.

The afore functionality is available through use of http_auth_request_module. However this module is not compiled by default. This got me the idea that would be nice to exercise going step by step through compiling Nginx with auth module Centos 7.

Yes – I do know that there are solutions on the market/internet which would save me from this – however I value the learning process in this challenge as well 🙂 If you have interesting links to alternatives please leave them in the comment section.

Getting the sources

Our journey begins with getting the sources. I have tried following the official Nginx documentation but I find it …. somehow not up to the task. Hence there are some modifications or additions that I did to get this through 🙂

mkdir nginx-from-source && cd $_

Once we have our new folder we can download the pre-reqs

Here we are taking Nginx version 1.19.0 – please be sure to check whats the latest version before running the command

   wget https://ftp.pcre.org/pub/pcre/pcre-8.44.tar.gz
   wget http://zlib.net/zlib-1.2.11.tar.gz
   wget http://www.openssl.org/source/openssl-1.1.1g.tar.gz
   wget https://nginx.org/download/nginx-1.19.0.tar.gz
   tar zxf nginx-1.19.0.tar.gz

Compile PCRE

tar -zxf pcre-8.44.tar.gz
cd pcre-8.44
./configure
make
sudo make install

Compile ZLIB

tar -zxf zlib-1.2.11.tar.gz
cd zlib-1.2.11
./configure
make
sudo make install

Compiling OpenSSL

OpenSSL deserves spot for bit more insights than just dry code. We will use never version than the one running on the box right now.

Pre-reqs

We will start off by installing required packages via yum and extracting the content of downloaded archive

yum group install 'Development Tools'
yum install perl-core zlib-devel -y
tar -xf openssl-1.1.1g.tar.gz 
cd openssl-1.1.1g

Configure & install OpenSSL

sudo ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib
sudo make
sudo make test
sudo make install

Configure shared libraries

Navigate to /etc/ld.so.conf.d and run the following

sudo echo "/usr/local/ssl/lib" >> /etc/ld.so.conf.d/openssl-1.1.1g.conf

ldconfig is used to create, update and remove symbolic links for the current shared libraries based on the lib directories present in the /etc/ld.so.conf

Reload with verbose

sudo ldconfig -v

Configure OpenSSL binary

Start with backing up the current OpenSSL

sudo mv /bin/openssl /bin/openssl.backup

Create script which will be executed on the system…

sudo vi /etc/profile.d/openssl.sh

… and set contents to

OPENSSL_PATH="/usr/local/ssl/bin"
export OPENSSL_PATH
PATH=$PATH:$OPENSSL_PATH
export PATH

Once done we need to make sure that the script is allowed to be executed

sudo chmod +x /etc/profile.d/openssl.sh

Next reload the profile to get the openSSL new binary with your PATH

source /etc/profile.d/openssl.sh

Verify openSSL version

 which openssl
 openssl version -a

If you reached this moment then we are ready to move on the next part 🙂

Compiling Nginx with extra modules

Create user under which the process will be running

useradd -s/sbin/nologin -d/usr/local/nginx -M nginx

Navigate to folder with nginx sources created during download of our pre-reqs and run the config command

./configure 
--user=nginx 
--group=nginx 
--error-log-path=/var/log/nginx/error.log 
--http-log-path=/var/log/nginx/access.log 
--sbin-path=/usr/local/nginx/nginx 
--pid-path=/usr/local/nginx/nginx.pid 
--with-pcre=../pcre-8.44 
--with-zlib=../zlib-1.2.11 
--with-http_auth_request_module 
--with-http_geoip_module 
--with-http_gzip_static_module 
--with-http_gunzip_module 
--with-http_realip_module 
--with-http_secure_link_module 
--with-http_slice_module 
--with-http_ssl_module 
--with-http_v2_module

The above compiles Nginx with extra modules. For a comprehensive list with detailed information about each of the extra modules please refer to official Nginx documentation.

Once the above command finishes run

  make
  make install

When the above process finishes you should have nginx installed in /usr/local/nginx

Initial configuration of Nginx

In order to use Nginx we need to configure it. Right now our system knows nothing about running it.

Run vi /etc/systemd/system/nginx.service and set the content to

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/nginx.pid
ExecStartPre=/usr/local/nginx/nginx -t
ExecStart=/usr/local/nginx/nginx
ExecReload=/usr/local/nginx/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Now when you run systemctl status nginx you should see our service available.

Go ahead and run it! Type systemctl start nginx

At this moment you should have Nginx running with extra modules compiled!

2

Serverless Okta JWT as AWS API Gateway Authorizer

About this solution

In todays technological world it has become very popular ( and quite easy )  to create serverless architectures with Lambdas and expose them via API gateway.

The expose part is something which we could protect better. Solution provided here is basic blueprint which leverages openID  ( in this case set up in Okta ).

 

This software/code is provided to you “as-is” and without warranty of any kind, express, implied or otherwise, including without limitation, any warranty of fitness for a particular purpose

 

Tech involved

Although I would like to keep the solution requirements to minimal this requires some software/services to work nicely together. Below I highlighted what we will need:

  • Serverless – to create and configure the stack
  • AWS – to consume provided services
  • Okta –  to provide authentication

Setting up the solution

Setup of the whole solution involves several steps. I have outlined them below.

Create Okta openID application

  • Login to your Okta organisation and navigate to Admin part
  • Go to application
  • Create new native application with `openID` ( this is the only option atm)
  • For the name we have used `OpenID – myAppName`
  • For the redirect URL you can paste `api://myAppName` ( or anything else if you plan to use different kind of flows )
  • Save the app and proceed to editing.
  • In general settings you need to modify *Allowed grant types* and enable `Resource Owner Password`
  • In client credentials enable `Use Client Authentication` ( make note of Client Id and Client Secret)
  • Assign the application to engineers which should have access to REST API

As a nice feature you could use claims to determine who can execute READ/WRITE actions

 

Verify token generation

In order to verify that you can get tokens from the app you have just created you need to call one of Okta endpoints.

  • Create basic authentication credentials consisting of the following client_id:client_secret
    You can use the following snippet

    echo "client_id:client_secret" | base64
  • With the result of that command you will be able to make calls using Authorization: Basic <result-of-command>
  • Make http call to token endpoint , making sure to replace your-okta-tenant-name , username and password
    curl -X POST \
    https://xxx.okta-emea.com/oauth2/default/v1/token \
    -H 'Authorization: Basic MG9hMmQzN.........Q==' \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d 'username=username&password=Password1&grant_type=password&scope=openid'

     

  • In response you should receive token in the following format
    {
    "access_token": "eyJraW.....BvXdkU2Gg",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid",
    "id_token": "eyJr....yg"
    }

 

Obtain a public key from Okta

At this point of time you have fully working Okta openID app and can obtain tokens. Our next task is to obtain public key which will allow us in later stage to verify token signature.

There are many ways to obtain the key – and each of them can either take more time or involves you providing more information. My idea was simple – automate it as much as possible… therefore I came up with go-jwk-pem (available in github ) . It is a simple CLI tool which takes either token or Okta server URL and retrieves public key which have been used to sign the JWT.

In this instance I will just use token from previous step

go-jwk-pem from-token --token eyJraW.....BvXdkU2Gg | /usr/bin/env ruby -e 'p ARGF.read'

Result of this command is single line public key , which is last piece of our puzzle which we need to make our solution working.

"-----BEGIN RSA PUBLIC KEY-----\nMIIBIjA........A4\nzTsuZ+eQLfhNbuA.....wWtcDsd+vMUlS7iJow\n2QIDAQAB\n-----END RSA PUBLIC KEY-----\n\n"

 

Creating AWS stack using Serverless

The time has come when we begin real fun 🙂 Let’s begin by cloning the solution from Github

One thats done we need to modify value in serverless.env.yml

dev:
  OKTA_PUBLIC_KEY: <PASTE-YOUR-PEM-HERE>

Since this functions are written in go we need to build them before deploying

> [SHELL]  RafPe $ make build
env GOOS=linux go build -ldflags="-s -w" -o bin/func1 func1/main.go
env GOOS=linux go build -ldflags="-s -w" -o bin/auth auth/main.go

And now let’s deploy by using one of profile for AWS ( from credentials file ) running simple command

> [SHELL]  RafPe $ sls deploy -s dev --aws-profile myAwsProfile --verbose

Output shows us details about our functions deployes ( this is minimal blueprint so your output can have more )

Serverless: Stack update finished...
Service Information
service: test-auth
stage: dev
region: eu-west-1
stack: test-auth-dev
api keys:
  None
endpoints:
  ANY - https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello
functions:
  func1: test-auth-dev-func1
  auth: test-auth-dev-auth
layers:
  None

Stack Outputs
AuthLambdaFunctionQualifiedArn: arn:aws:lambda:eu-west-1:123:function:test-auth-dev-auth:3
Func1LambdaFunctionQualifiedArn: arn:aws:lambda:eu-west-1:123:function:test-auth-dev-func1:3
ServiceEndpoint: https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev
ServerlessDeploymentBucketName: test-auth-dev-serverlessdeploymentbucket-2g5ap50n5lwn

 

Testing the solution

So right now we can immediately check if our setup works. Let’s start by trying to make simple http call

> [SHELL]  RafPe $ http https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Date: Sat, 15 Dec 2018 11:01:24 GMT
Via: 1.1 bce55e537f8dfcf0127f649d11fd1821.cloudfront.net (CloudFront)
{
    "message": "Unauthorized"
}

As expected we get Unauthorized message 😉 Let’s try to add token generated by Okta and make the call again

> [SHELL]  RafPe $ http https://reiw2emcp3.execute-api.eu-west-1.amazonaws.com/dev/hello Authorization:'Bearer eyJraWQ....QHMi5ISw'
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 69
Content-Type: application/json
Date: Sat, 15 Dec 2018 11:10:04 GMT
Via: 1.1 94d63cbf92082237b86267ffd4cacc64.cloudfront.net (CloudFront)
X-Cache: Miss from cloudfront
X-MyCompany-Func-Reply: world-handler
{
    "message": "Okay so your other function also executed successfully!"
}

and voilla 😉 we have just created custom authorizer validating our Okta JWT.

 

Summary

Although this is just a blueprint it can be nicely extended. I would like to point out several items you might be interested about this

  • Solution can be nicely extended to use claims to provide appropriate access – I find it really nice
  • You are not limited to use goLang because of one function being written in go. Serverless support multiple frameworks – just need to define those on function level then ( and define what packages you are including )

 

if you have any feedback – please leave comment or add your code into github repo 😉