*for the Decentralised Auth Callout flow
A configurable NATS micro-service that exchanges Identity Provider (IdP) tokens1 for NATS user authorization JWTs. This component helps implement the decentralised auth callout flow:
1https://www.iana.org/assignments/jwt/jwt.xhtml
The assumption is that implementations of auth callout microservices are almost identical, but for the process of determining which authorizations should be assigned to the minted NATS user access tokens.
This component implements the boilerplate code and using YAML configuration and golang templating, allows construction of JWTs with claims of the form:
claims(NATS_JWT) = f(claims(idpJwt))
where:
f
is a function that extracts and transforms claims from the IdP's Jwt.idpClaims
is the IdP JWT supplied from the user.The mock OIDC/OAuth2 service https://oidctest.wsweet.org/ is used to obtain an authenticated JWT id_token
.
The example scripts in the scripts/
and examples/
folders demonstrate how to configure, deploy and test this micro-service. These are structred as:
scripts
*-toolkit.sh
, helper bash functions./templates/*
, templates to use with vault/openbao and vault-plugin-secret-nats.examples
/<example_name>
/<name>_initial_setup.sh
, one-time script to setup decentralised accounts and users/<name>_start_service.sh
, configures and starts the auth callout micro-service/<name>_simulate_login.sh
, contacts IdP, authenticates, and uses the obtained token to establish a client connection with NATS/run.sh
, run all steps in the exampleAssuming docker is installed, the examples can be run using: make example-<example_name>
.
The RGB.org
organisation has three departments that share a single NATS deployment. A department consists of several teams, with each team developing a set of applications.
The blue department has this setup.
there are three teams: App Team 1
, App Team 2
, App Team 3
App Team
i
's apps have NATS credentials minted by NATS account APPi
the department has configured and deployed a shared instance of the nats-iam-broker microservice.
minter
of NATS account MINT_11
.nobody
, that has no permissions.Bob is a member of App Team 3
, and wishes to use their in-house app demo-app
. This section describes the authentication and authorization flow.
Bob launches demo-app
.
demo-app
directs him to OIDC Provider2
for authentication, which Bob completes.demo-app
receives back a signed JWT token jwt.provider2.bob
.demo-app
obtains credentials1 for MINT_11(nobody)
and packages this into a NATS authorization_request
(perhaps by calling nats.connect()
)2.
NATS creates a connection_id
for the Bob's instance of demo-app
, and directs the MINT_11(nobody)
connection to the blue department's nats-iam-broker microservice. This is because the microservice connects to NATS using MINT_11(minter)
, and the two have a common NATS account. The connection between NATS and nats-iam-broker is private3.
nats-iam-broker microservice receives the request.
jwt.provider2.bob
and validates the token against OIDC Provider2
.Authorization Violation
back to the user.nats-iam-broker microservice mints a new JWT.
jwt.provider2.bob
.APP3
.demo-app
usage.nats-iam-broker signs the minted JWT, encrypts it for transport and sends it to NATS server.
NATS server decrypts3 and validates the nats-jwt, and binds the authorizations to the client's connection_id
. Finally, it notifies Bob's instance of demo-app
of the successful connection.
1 although nobody
user credentials have no NATS permissions, storing them externally can facilitate key rotation of signing_key(MINT_11)
.
2 a (somewhat) arbitrary decision has been made to pass the third-party JWT in the password field, i.e., nats.Connect(UserCredentials(MINT_11(nobody)), password=jwt.provider2.bob)
3 this uses the XKey
field. It is optional but recommended.
This example demonstates a simpler setup consisting of:
MINT
, with standard users minter
and nobody
APP1
As in the previous example, authorised NATS users are created and signed on-the-fly, following successful validation of the IdP token.
The application is intended to be deployed as either:
Standard go templating is used to construct roles dynamically. Templating functions are listed in the filters
module.
The configuration structure is outlined here.
Key | Type | Description |
---|---|---|
nats.url |
string |
NATS server URL |
service.name |
string |
name of deployed micro-service instance |
service.version |
string |
version of deployed micro-service instance |
service.description |
string |
description of deployed micro-service instance |
service.creds_file |
string |
user credentials used to connect to NATS |
service.account.name |
string |
(metadata) human-readable reminder of account used to sign and encrypt communications with NATS server |
service.account.signing_nkey |
string |
key used to sign new user-jwt returned to NATS |
service.account.encryption.enabled |
bool |
toggle for communication-encryption with NATS server |
service.account.encryption.xkey_secret |
string |
trusted secret-key for encryption with NATS server |
nats_jwt.exp_max |
duration |
maximum duration of minted nats user-jwt |
idp.client_id |
string |
the client identifier registered with the IdP |
idp.issuer_url |
string |
the url of the IdP issuer |
idp.validation.claims |
[]string |
set of required claims on idp token |
idp.validation.aud |
[]string |
set of allowed values for audience claim |
idp.validation.exp.min |
duration |
minimum time to expiry for idp token from now |
idp.validation.exp.max |
duration |
maximum duration of idp token from now |
rbac.user_accounts |
- | set of accounts configured to issue and sign nats user-jwts |
rbac.user_accounts[i].name |
string |
name of user-jwt signing account |
rbac.user_accounts[i].public_key |
string |
public key of user-jwt signing account |
rbac.user_accounts[i].signing_nkey |
string |
signing key of user-jwt signing account in nkey format |
rbac.roles |
- | set of referenceable nats jwt permission groupings |
rbac.roles[i].name |
string |
role name |
rbac.roles[i].permissions |
jwt.Permissions | nats-io/jwt permissions structure (see link) |
rbac.roles[i].limits |
jwt.Limits | nats-io/jwt limits structure (see link) |
rbac.role_binding[i].user_account |
string |
user account to bind |
rbac.role_binding[i].roles |
[]string |
set of roles to bind |
rbac.role_binding[i].match.claim |
string |
name of IdP JWT claim to match on |
rbac.role_binding[i].match.value |
string |
corresponding value of IdP JWT claim to match on |