This is a LUA module for nginx
which will verify authentication cookie presented in requests. If cookie is missing or is invalid, it will send a redirect to SSO (single-sign-on) service.
Please make sure that your NGINX installation contains support for LUA files.
For example Debian includes LUA in nginx-extras
package (nginx-light, nginx, nginx-full won't work).
It caters for domain it is running, so no configuration is necessary. Use is thus super simple, this is example of grafana service:
location / {
access_by_lua_file /opt/showmax/nginx-sso-cookie-auth/sso_cookie_auth.lua;
proxy_pass http://127.0.0.1:6081;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto "https";
}
So adding
access_by_lua_file /opt/showmax/nginx-sso-cookie-auth/sso_cookie_auth.lua;
to your location configuration should be enough to get you protected (+ you have to install this package).
Return URL is normally taken from nginx
and you don't need to care about it. Sometimes you need to able to override it. Especially when you are behind other proxy. You can do it with
set $sso_return_url 'https://kibana.showmax.cc';
Part of the authentication data is also audience. For list of values, please check the SSO project.
set $sso_allowed_audience 'showmax';
Will require to have Showmax account to get access. You don't need to specify this though as it is default value. So you don't need to change anything in your configuration if it is in front of internal service.
If you don't care for audience (aka looking for valid account only) use any
as value:
set $sso_allowed_audience 'any';
You can also specify multiple values. They are treated as having OR between then. So for example
set $sso_allowed_audience 'showmax, recombee';
Will give access to account which is either in showmax
, or in recombee
or both (first match will win anyway).
Note: I was thinking (and initially implemented AND option). But it turned out, that OR would be more useful, at least for now. We can add it later with e.g. sso_required_audience
option.
We are now passing authentication cookie Showmax-Auth-Data
which contains JSON with additional data. You can find description of the fields in https://git.showmax.cc/ops/ops-sso project. Some of data are copied for convenience into request headers. Those are:
X-Forwarded-User
==uid
X-Forwarded-Audience
== list of authenticated audience values from SSO
As you can see, module has multiple configuration options hard-coded. We have decided not to go with external configuration file, as that would tremendously complicate deployment (you would need to set Lua paths etc). Our deployment looks like this:
- we pull this repository as
upstream
branch into our internal repo, which is following thegbp
standards. - Debian patch queue is used for replacing placeholders with actual configuration.
- resulting Debian package is taken by Puppet and deployed to appropriate hosts
There is a variable called sso_domain_match
. It's purpose is to make sure, that you are allowing only domains you cater for as also to allow support for multiple domains with the same deployed module. The line reads as follows in our deployment:
local sso_domain_match = ngx.re.match(ngx.var.host, "showmax\.(cc|io|com)$")
Module is designed to work with multiple domains. Thus signing keys stored in keys
variable is a hash, where key is the signing domain. For example configuration for two TLDs will look like:
local keys = {}
keys["showmax.cc"] = "PheeFahsiTh1Tooreemuegh9gie7epho0Lahjoh9tia1ceef0neiFi4WeiPiD9ah"
keys["showmax.io"] = "ail9iegheiTh2shooyie3roediagash4aelaishahchoghah0gae7rao0dohch6p"
You need to set names of cookies which are going to be set by your SSO server (see down below). Module expects to get cookie values in cookie_auth_data
and cookie_auth_sign
variables. So simple fetch the appropriate cookies via nginx
interface:
local cookie_auth_data = ngx.unescape_uri(ngx.var.cookie_showmaxAuthData)
local cookie_auth_sign = ngx.unescape_uri(ngx.var.cookie_showmaxAuthSign)
Authentication cookie is generated by SSO service, which is simple Sinatra application in our case. This app is quite environment specific, so we are not really pushing to anybody. In our case it consults our LDAP for username/password and then does a TOTP (second factor authentication) check.
The actual code, which is setting cookies on our side looks like this (exact copy and past from the source):
expiration = Time.now + 24 * 3600
msg = expiration.to_i.to_s
data = Base64.urlsafe_encode64(MultiJson.dump({
exp: expiration.to_i,
auth_source: session[:auth_source],
uid: session[:uid],
dn: session[:dn],
email: session[:email],
name: session[:name],
aud: session[:aud],
}))
data_sign = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), settings.cookie_secret, data)
response.set_cookie("showmaxAuthData", {
value: data,
domain: settings.cookie_domain,
path: '/',
httponly: true,
secure: true,
})
response.set_cookie("showmaxAuthSign", {
value: data_sign,
domain: settings.cookie_domain,
path: '/',
httponly: true,
secure: true,
})
Where settings.cookie_domain
resolves to appropriate domain, such as .showmax.cc
. You can also see use of session
object. Which is an encrypted session between SSO server and the client. It contains information such as uid
etc. You can replace this with your database for example. settings.cookie_secret
is the same secret you would set to keys
for this domain (.showmax.cc
in this case) in Lua module.
Placeholder image will be returned for request believed to be requesting an image (based on Accept
header). We have implemented this as we got quite a lot of confused users, when they have been embedding SSO protected images into their documents.
Placeholder image is an Public Domain Fire Ball Icon.