The following is the default configuration; you can use it to create your own Night's Watch
configuration.
oidc:
issuerUri: https://issuer.castle_black.com
client_id: the_wall
client_secret: the_key_of_the_wall
redirect_uri: https://north.7kingdoms.com/oidc/callback
scopes: openid profile email offline_access
# https://github.com/expressjs/cookie-session
cookie:
name: nightswatch
keys:
- you_know_nothing_jon_snow
maxAge: 24h
targets:
path: /**
upstream: http://httpbin.org
routes: []
# - path: /_dev
# upstream: http://httpbin.org
rewrite: []
# - match: ^/_dev
# rewrite: '/headers'
storage:
kind: InMemory
specs:
stdTTL: 24h
server:
# max_body_limit: 100k
http:
enable: true
port: 3000
# to be implemented
https:
enable: false
port: 3001
max_header_size: 8192
proxy:
# http://expressjs.com/en/guide/behind-proxies.html
# loopback - 127.0.0.1/8, ::1/128
# linklocal - 169.254.0.0/16, fe80::/10
# uniquelocal - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7
- loopback
- linklocal
- uniquelocal
# add your own reverse proxy ip or a network
# - x.x.x.x/x
healthchecks:
readiness: /healthcheck/ready
liveness: /healthcheck
timeout: 2s
relying_party:
on_success_redirect: /
on_fail_redirect: /
oidc_base_path: /oidc
oidc_paths:
login: /login
callback: /callback
rules:
- route: /**
methods:
- all
logLevel: error
headers:
prefix: X-AUTH
proxy:
access-token: tokenset.access_token
id-token: tokenset.id_token
expires-at: tokenset.expires_at
expires-in: tokenset.expires_in
sub: idtoken.sub
name: idtoken.name
email: idtoken.email
family-name: idtoken.family_name
given-name: idtoken.given_name
All of the options can be provided as ENVIRONMENT variables by applying this rule:
replace('.', '__').toUpperCase()
In order to set the HTTP headers prefixes that we're going to send to the upstream, we can use the config.yaml file:
relying_party:
headers:
prefix: X-AUTH
A variable named RELYING_PARTY__HEADERS__PREFIX
could also be set, instead.
The mandatory configuration part is the following:
With a config.minimal.yaml
oidc:
issuerUri: https://issuer.castle_black.com
client_id: the_wall
client_secret: J0n_Sn0w_is_Aeg0n_T@rg@ryen
redirect_uri: https://north.7kingdoms.com/oidc/callback
cookie:
keys:
- you_know_nothing_jon_snow
targets:
upstream: http://httpbin.org
and here is its alternative, through a .env file or environment variables:
OIDC__ISSUERURI=https://issuer.castle_black.com
OIDC__CLIENT_ID=the_wall
OIDC__CLIENT_SECRET=J0n_Sn0w_is_Aeg0n_T@rg@ryen
OIDC__REDIRECT_URI=https://north.7kingdoms.com/oidc/callback
COOKIE__KEYS_0=you_know_nothing_jon_snow
TARGET__UPSTREAM=http://httpbin.org
You can run Night's Watch in different scenarios:
Run with node 🐳 Run with Docker 🐳 Run with Docker Compose ⎈ Run in Kubernetes: (soon)
Night's Watch is developed using NodeJS 12; check your installed version with node --version
or install it from the official website.
Once you have checked the node version, you can go ahead and clone the repository:
$ git clone https://github.com/iad-os/nightswatch.git
next, run npm install
to download dependencies and, finally, finally npm start
or npm run start-pretty
, for a prettier console logging.
In order to pass environment variables, a .env
file can be created in the checkout folder; otherwise, they can be passed with this npm start command:
CONFIG_FILE=./recipes/simple/config.simple.yaml npm run start
.
It is also possible to use a .yaml configuration:
$ cp /src/config.default.yaml ./config.yaml
Use your own editor to configure Night's Watch the way you need it, then run it with npm start
or npm run start-pretty
.
If not overridden, the CONFIG_FILE is set to ./config.yaml
by default and Night's Watch will try to read your configuration from config.yaml
in the current folder.
The official Night's Watch docker image is available at Docker Hub iad2os/nightswatch and can be executed with the following command:
$ docker run \
-e OIDC__ISSUERURI=https://issuer.castle_black.com \
-e OIDC__CLIENT_ID=the_wall \
-e OIDC__CLIENT_SECRET=J0n_Sn0w_is_Aeg0n_T@rg@ryen \
-e OIDC__REDIRECT_URI=https://north.7kingdoms.com/oidc/callback \
-e COOKIE__KEYS_0=you_know_nothing_jon_snow \
-e TARGET__UPSTREAM=http://httpbin.org \
-p 3000:3000 \
iad2os/nightswatch
a volume mount or a .env file can also be used modifying the docker run
as follows:
(with volumes)
$ docker run \
-v /path/to/config.yaml:/app/config.yaml
-p 3000:3000 \
iad2os/nightswatch
(with .env)
$ docker run \
-env /path/to/.env
-p 3000:3000 \
iad2os/nightswatch
Let's start creating a docker compose file, in this example scenario we will make http://httpbin.org safe with the aid of the Night's Watch. This will come in handy later, while verifying that everything works as expected.
version: '3.4'
services:
nightswatch:
image: iad2os/nightswatch
volumes:
- ./config-simple.yaml:/app/config.yaml
environment:
DEBUG: nightswatch:*
CONFIG_FILE: ./config.yaml
ports:
- 3000:3000
healthcheck:
test:
[
'CMD',
'wget',
'--quiet',
'--spider',
'http://localhost:3000/healthcheck',
]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
With Night's Watch you can easily customize the headers passed to the Resource Server (in other words, your application).
For each request, Night's Watch will add some headers that a resource server may consume to bind the identity and other details needed to handle such request.
Here is the default headers and its configuration:
relying_party
headers:
prefix: X-AUTH
proxy:
access-token: tokenset.access_token
id-token: tokenset.id_token
expires-at: tokenset.expires_at
expires-in: tokenset.expires_in
sub: idtoken.sub
name: idtoken.name
email: idtoken.email
family-name: idtoken.family_name
given-name: idtoken.given_name
you can configure your own headers and decide what to send to the upstream, north of the Wall!
Every request comes with 3 objects:
here is a JSON example:
{
"tokenset": {
"access_token": "theAccessToken",
"expires_at": 1583084430,
"refresh_expires_in": 0,
"refresh_token": "theRefreshToken",
"token_type": "bearer",
"id_token": "oidcIdToken",
"not-before-policy": 0,
"session_state": "aRandomId",
"scope": "openid offline_access email profile"
},
"userinfo": {
"sub": "459697e5-6c58-45d8-88f2-2a4ea5b3157a",
"email_verified": true,
"name": "Jeor Mormont",
"preferred_username": "The Old Bear",
"given_name": "Jeor",
"family_name": "Mormont",
"email": "theoldbear@castle_black.com"
},
"idtoken": {
"jti": "88ae774f-4121-4a9a-8584-2a8d13831130",
"exp": 1583084430,
"nbf": 0,
"iat": 1583084370,
"iss": "https://issuer.castle_black.com",
"aud": "1min-access-token",
"sub": "459697e5-6c58-45d8-88f2-2a4ea5b3157a",
"typ": "ID",
"azp": "1min-access-token",
"auth_time": 1583084369,
"session_state": "99bd9ca8-cd06-469c-90ea-4fddcc3bcdee",
"acr": "1",
"email_verified": true,
"name": "Jeor Mormont",
"preferred_username": "The Old Bear",
"given_name": "Jeor",
"family_name": "Mormont",
"email": "theoldbear@castle_black.com"
}
}
For instance, you could add a header named X-AUTH-ROLES
, representing user roles.
using an environment variable:
RELYING_PARTY__HEADERS_PROXY_ROLES=idtoken.roles
using config.yaml
relying_party
headers:
prefix: X-AUTH
proxy:
roles: idtoken.roles
An object named tokenset will be available with the following properties:
access_token: <string>
token_type: <string>
id_token: <string>
refresh_token: <string>
expires_in: <number>
expires_at: <number> Access token expiration timestamp, formed by the number of seconds since the epoch (January 1, 1970 00:00:00 UTC).
session_state: <string>
other properties may be present and they'll be passthrough available on the TokenSet instance
This is an example of TokenSet object:
tokenset
access_token: theAccessToken
expires_at: 1583084430
refresh_expires_in: 0
refresh_token: theRefreshToken
token_type: bearer
id_token: oidcIdToken
not fore-policy: 0
session_state: aRandomId
scope: openid offline_access email profile
for more details, check the official TokenSet documentation at panva/node-openid-client.
The UserInfo object contains the claims defined by the OIDC standards, and this can change between OIDC providers. If you also control the OIDC provider, consult the documentation to configure what claims are included in the UserInfo endpoint. More info about UserInfo and standard claims can be found at OIDC Specs - User Info OIDC Specs - Standard Claims
This is an UserInfo object example:
userinfo:
sub: 459697e5-6c58-45d8-88f2-2a4ea5b3157a
email_verified": true
name: Jeor Mormont
preferred_username: The Old Bear
given_name: Jeor
family_name: Mormont
email: theoldbear@castle_black.com
Even though you have a serialized and JWT-signed id_token located at the tokenset.id_token
level, the root-level idtoken
object contains the same content but, indeed, as an object.
With this, it will be more convenient to handle claims in the headers..
Here is an example of idtoken
object:
idtoken:
jti: 88ae774f-4121-4a9a-8584-2a8d13831130
exp: 1583084430
nbf: 0
iat: 1583084370
iss: https://issuer.castle_black.com
sub: 459697e5-6c58-45d8-88f2-2a4ea5b3157a
typ: ID
azp: 1min-access-token
auth_time: 1583084369
session_state: 99bd9ca8-cd06-469c-90ea-4fddcc3bcdee
acr: 1
email_verified: true
name: Jeor Mormont
preferred_username: The Old Bear
given_name: Jeor
family_name: Mormont
email: theoldbear@castle_black.com
Check out the official OIDC docs: OIDC Specs