This repository contains the materials and some examples you can use to learn the basic concepts of REST and IAM.
You can find more in-depth information in https://learning.intersystems.com.
- Git
- Docker (if you are using Windows, make sure you set your Docker installation to use "Linux containers").
- Docker Compose
- Visual Studio Code + InterSystems ObjectScript VSCode Extension
- Postman
- InterSystems IRIS IAM enabled license file.
You need to setup your access to InterSystems Container Registry to download IRIS limited access images.
Have a look at this Introducing InterSystems Container Registry on Developer Community.
- Log-in into https://containers.intersystems.com/ using your WRC credentials and get a token.
- Set up docker login in your computer:
docker login -u="user" -p="token" containers.intersystems.com
- Download images:
docker pull containers.intersystems.com/intersystems/iris:2023.1.1.380.0
docker pull containers.intersystems.com/intersystems/iam:3.0.2.0-4
IMPORTANT! Copy your InterSystems IRIS IAM enabled license file into the workshop root and rename it to iris.key
.
Build the image we will use during the workshop:
$ git clone https://github.com/intersystems-ib/workshop-rest-iam
$ cd workshop-rest-iam
$ docker-compose build
- Run the containers we will use in this section and check you access them:
docker-compose up -d irisA tools
- Access IRIS Management Portal using
superuser
/SYS
.
- We will use an OpenAPI specification as a starting point to build a REST API in IRIS.
- Check the YAML version in shared/leaderboard-api-v1.yaml and the JSON version in shared/leaderboard-api-v1.json
- Have a look at it using https://editor.swagger.io or https://app.swaggerhub.com/login.
- The sample API we are developing will use two main persistent (table) classes that will hold data for us.
- Have a look at Webinar.Data.Player and Webinar.Data.Team.
- Notice that both classes inherit from
%Persistent
and%JSON.Adaptor
. - If you are not familiar with
%JSON.Adaptor
and transforming objects to and from JSON, check this great article JSON Enhancements on Developer Community. - Check also the generated data through System Explorer > SQL.
- Let's build the API implementation skeleton from the OpenAPI specification using
^%REST
wizard. - Open a WebTerminal session using http://localhost:52773/terminal/ and type:
WEBINAR > do ^%REST
REST Command Line Interface (CLI) helps you CREATE or DELETE a REST application.Enter an application name or (L)ist all REST applications (L): L
Applications Web Applications
------------ ----------------
Enter an application name or (L)ist all REST applications (L): Webinar.API.Leaderboard.v1
REST application not found: Webinar.API.Leaderboard.v1
Do you want to create a new REST application? Y or N (Y): Y
File path or absolute URL of a swagger document.
If no document specified, then create an empty application.
OpenAPI 2.0 swagger: /shared/leaderboard-api-v1.json
OpenAPI 2.0 swagger document: /shared/leaderboard-api-v1.json
Confirm operation, Y or N (Y): Y
-----Creating REST application: Webinar.API.Leaderboard.v1-----
CREATE Webinar.API.Leaderboard.v1.spec
GENERATE Webinar.API.Leaderboard.v1.disp
CREATE Webinar.API.Leaderboard.v1.impl
REST application successfully created.
Create a web application for the REST application? Y or N (Y): Y
Specify web application name. Default is /csp/Webinar/API/Leaderboard/v1
Web application name: /leaderboard/api/v1
-----Deploying REST application: Webinar.API.Leaderboard.v1-----
Application Webinar.API.Leaderboard.v1 deployed to /leaderboard/api/v1
- Using VS Code, complete the code of the following methods in
Webinar.API.Leaderboard.v1.impl
.
ClassMethod addPlayer(body As %DynamicObject) As %DynamicObject
{
set player = ##class(Webinar.Data.Player).%New()
do player.%JSONImport(body)
set sc = player.%Save()
if $$$ISERR(sc) {
do ..%SetStatusCode(405)
quit ""
}
do player.%JSONExportToStream(.stream)
quit stream
}
ClassMethod getPlayers() As %DynamicObject
{
set sql = "SELECT Id, Name, Alias FROM Webinar_Data.Player order by Score"
set statement = ##class(%SQL.Statement).%New()
set sc = statement.%Prepare(sql)
set rs = statement.%Execute()
set array = []
while rs.%Next() {
do array.%Push(
{
"Id": (rs.%Get("Id")),
"Name": (rs.%Get("Name")),
"Alias": (rs.%Get("Alias")),
"Node": ($system.INetInfo.LocalHostName())
})
}
quit array
}
ClassMethod getPlayerById(playerId As %Integer) As %DynamicObject
{
set player = ##class(Webinar.Data.Player).%OpenId(playerId)
if '$isobject(player) {
do ..%SetStatusCode(404)
quit ""
}
do player.%JSONExportToStream(.stream)
quit stream
}
ClassMethod updatePlayer(playerId As %Integer, body As %DynamicObject) As %DynamicObject
{
set player = ##class(Webinar.Data.Player).%OpenId(playerId)
if '$isobject(player) {
do ..%SetStatusCode(404)
quit ""
}
do player.%JSONImport(body)
do player.%Save()
do player.%JSONExportToStream(.stream)
quit stream
}
ClassMethod deletePlayer(playerId As %Integer) As %DynamicObject
{
set sc = ##class(Webinar.Data.Player).%DeleteId(playerId)
if $$$ISERR(sc) {
do ..%SetStatusCode(404)
}
quit ""
}
- Configure the automatically created web endpoint called
/leaderboard/api/v1
in Web Applications. Set unauthenticated access and setWebinar
temporal role. - In Postman, import postman/leaderboard-api.postman_collection.json collection.
- Try these requests:
GET Player
,GET Players
,POST Player
yPUT Player
.
You'll plug the API implementation into a production, so you could use any feature of interoperability productions withing a REST API implementation.
- Using the Management Portal Interoperability > List > Productions > New, create a new production called
Webinar.Production
. - Click on
Production Settings
and in Settings tab make sureTesting enabled
is checked. - Let's create a simple Business Operation that you will use to request some information you will include in your REST API response. This component sends a REST message to an external dummy service and returns a value. Have a look at the source code src/Webinar/BO/DummyREST.cls
- Add a new Business Operation in your production, choose
Webinar.BO.DummyREST
. Then, in the Settings tab configure:- HTTP Server:
mockbin.com
- URL:
/request
- HTTP Server:
- Test the Business Operation in the Actions tab clicking on the Test button.
- In VS Code, open your REST API implementation src/Webinar/API/Leaderboard/v1/impl.cls.
- Update the class to extends from
(%REST.Impl, Ens.BusinessService)
. Now, you have turned your REST API into a Business Service. - In the Webinar.Production production configuration page, add a new Business Service and select
Webinar.API.Leaderboard.v1.impl
. Keep it disabled. - Back in VS Code, change the src/Webinar/API/Leaderboard/v1/impl.cls
getPlayerById
implementation:
ClassMethod getPlayerById(playerId As %Integer) As %DynamicObject
{
set player = ##class(Webinar.Data.Player).%OpenId(playerId)
if '$isobject(player) {
do ..%SetStatusCode(404)
quit ""
}
// instantiate Business Service (interoperability framework)
set sc = ##class(Ens.Director).CreateBusinessService("Webinar.API.Leaderboard.v1.impl",.service)
$$$ThrowOnError(sc)
// build request message
set req = ##class(Ens.StringContainer).%New()
set req.StringValue = playerId
// send message to Business Operation
set sc = service.SendRequestSync("Webinar.BO.DummyREST", req, .rsp)
$$$ThrowOnError(sc)
// concatenate Business Operation response to REST API response
set player.Name = player.Name_"("_rsp.StringValue_")"
do player.%JSONExportToStream(.stream)
quit stream
}
- Enable the
Webinar.API.Leaderboard.v1.impl
Business Service in production configuration page. - In Postman, test the
GET Player
request and check Message Viewer messages and visual trace. - IMPORTANT! In order to continue the next sections, stop the production and revert the changes on src/Webinar/API/Leaderboard/v1/impl.cls
getPlayerById
to continue. Test again theGET Player
request in Postman and check it's OK.
Run API manager container and access IAM Management Portal:
cd iam
docker-compose up -d
Now, you will build a basic scenario to manage the REST API in InterSystems API Manager (IAM).
Remember IAM can be managed using the UI or using the REST interface.
Tip: open a VS Code Terminal session and type the following so you can send curl
commands to IAM.
docker exec -it tools sh
- Add a service to which will invoke the API in IRIS.
curl -X POST --url http://iam:8001/services/ \
--data 'name=iris-leaderboard-service' \
--data 'url=http://irisA:52773/leaderboard/api/v1' | jq
- Add some routes that will give access to the service you have just created.
curl -X POST --url http://iam:8001/services/iris-leaderboard-service/routes \
--data 'paths[]=/leaderboard' \
--data 'name=leaderboard-GET' \
--data 'methods[]=GET'| jq
curl -X POST --url http://iam:8001/services/iris-leaderboard-service/routes \
--data 'paths[]=/leaderboard' \
--data 'name=leaderboard-POST' \
--data 'methods[]=POST'| jq
curl -X POST --url http://iam:8001/services/iris-leaderboard-service/routes \
--data 'paths[]=/leaderboard' \
--data 'name=leaderboard-PUT' \
--data 'methods[]=PUT'| jq
- Using the request-transformer plugin you can add Basic authentication headers to incoming requests so it will authenticate in IRIS.
curl -i -X POST \
--url http://iam:8001/services/iris-leaderboard-service/plugins \
--data 'name=request-transformer' \
--data 'config.add.headers=Authorization:Basic c3VwZXJ1c2VyOlNZUw==' \
--data 'config.replace.headers=Authorization:Basic c3VwZXJ1c2VyOlNZUw=='
- In Postman, test the
IAM - Get Player - No auth
request.
- Add Authentication by setting up the
key-auth
plugin in the service.
curl -X POST http://iam:8001/services/iris-leaderboard-service/plugins \
--data "name=key-auth" | jq
- In Postman, test again the
IAM - Get Player - No auth
request.
- Create some consumers so you can authenticate to access the API.
- Create consumer
systemA
curl -d "username=systemA&custom_id=SYSTEM_A" http://iam:8001/consumers/ | jq
- Create secret for `systemA``
curl -X POST http://iam:8001/consumers/systemA/key-auth -d 'key=systemAsecret' | jq
- In Postman, test
IAM - GET Player. Consumer SystemA
request. - Create another consumer called
webapp
curl -d "username=webapp&custom_id=WEB_APP" http://iam:8001/consumers/ | jq
- Create secret for
webapp
curl -X POST http://iam:8001/consumers/webapp/key-auth -d 'key=webappsecret' | jq
- In Postman, test
IAM - GET Players - Consumer WebApp
request.
- We can simulate some traffic using shared/simulate.sh script in your tools container:
docker exec -it tools sh
/shared/simulate.sh
- Add a restriction for
webapp
consumer. Limit it to 100 requests in a minute.
curl -X POST http://iam:8001/consumers/webapp/plugins \
--data "name=rate-limiting" \
--data "config.minute=100" | jq
- Remove the restriction using the IAM Portal so you can continue.
- Set up the Developer Portal in IAM so developers could sign up automatically.
- Go to IAM Portal and
Dev Portal > Settings
: - Set
Authentication Plugin=Basic
- Set
Auto Approve Access=Enable
- Set
Session Config (JSON)=Custom
and enter:
{
"cookie_name": "portal_session",
"secret": "CHANGE_THIS",
"storage": "kong",
"cookie_secure": false
}
- Save Changes
- Publish the OpenAPI specs of the REST API you have just built in IAM Portal and
Dev Portal > Editor
- Click on
New File +
and setFile Type=spec
andFile Path=leaderboard.yaml
. - Copy the content of leaderboard-api-v1.yaml.
- Go to the Developer Portal and click
Sign Up
. - Logged as a developer, create your own API credential in
Create API Credential
. - In Postman, test
IAM - Get Players - Developer
replacing theapi-key
header by the actual credential you have just created. - Access the APIs documentation in
Documentation
.
- There are different ways of exposing the audit logs. For instance, you can configure a global http log plugin to push logs to your remote audit interface.
- In this case you can use a very simple REST audit interface that will audit IAM requests into
shared/audit.json
file.
curl -X POST http://iam:8001/plugins/ \
--data "name=http-log" \
--data "config.http_endpoint=http://irisA:52773/audit/log" \
| jq
- Try again some IAM requests in Postman and check the audit file.
In this scenario, you will need a second IRIS instance:
docker-compose up -d irisB
You will build a load balancing scenario between two IRIS instances with the leaderboard REST API.
This can be useful in case you want to spread the workload, blue-green deployment, etc.
Tip: open a VS Code Terminal session and type the following so you can send curl
commands to IAM.
docker exec -it tools sh
- Create an upstream
curl -s -X POST http://iam:8001/upstreams \
-d name=leaderboard-lb-stream \
| jq
- Add the two IRIS instances targets to upstream
curl -s -X POST http://iam:8001/upstreams/leaderboard-lb-stream/targets \
-d target=irisA:52773 \
-d weight=500 \
| jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-lb-stream/targets \
-d target=irisB:52773 \
-d weight=500 \
| jq
- Update your service to point to the upstream:
curl -X PATCH http://iam:8001/services/iris-leaderboard-service \
--data host='leaderboard-lb-stream' | jq
- In Postman, test the
IAM - GET Players (LB)
request. Pay attention to theNode
property in the response body.
In this scenario, you will need a third iris instance:
docker-compose up -d irisC
You will now build a route by header scenario using three IRIS instances with the leaderboard REST API.
This could be useful in case you want use different servers depending on request headers (e.g. different versions).
Tip: open a VS Code Terminal session and type the following so you can send curl
commands to IAM.
docker exec -it tools sh
- Create Default, V1 and V2 upstreams
curl -s -X POST http://iam:8001/upstreams \
-d name=leaderboard-header-stream \
| jq
curl -s -X POST http://iam:8001/upstreams \
-d name=leaderboard-header-v1-stream \
| jq
curl -s -X POST http://iam:8001/upstreams \
-d name=leaderboard-header-v2-stream \
| jq
- Add targets to each IRIS instance
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-stream/targets \
-d target=irisA:52773 \
| jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-v1-stream/targets \
-d target=irisB:52773 \
| jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-v2-stream/targets \
-d target=irisC:52773 \
| jq
- Update your service to point to the upstream:
curl -X PATCH http://iam:8001/services/iris-leaderboard-service \
--data host='leaderboard-header-stream' | jq
- Add
route-by-header
plugin with some conditions on request headerversion
:
curl -s -X POST http://iam:8001/services/iris-leaderboard-service/plugins \
-H 'Content-Type: application/json' \
-d '{"name": "route-by-header", "config": {"rules":[{"condition": {"version":"v1"}, "upstream_name": "leaderboard-header-v1-stream"}, {"condition": {"version":"v2"}, "upstream_name": "leaderboard-header-v2-stream"}]}}' \
| jq
- In Postman, try the
IAM - GET Players (Route By Header)
using differentversion
header request values.
decK helps manage Kong’s configuration in a declarative fashion. This means that a developer can define the desired state of Kong Gateway – services, routes, plugins, and more – and let decK handle implementation without needing to execute each step manually, as you would with the Kong Admin API.
- Open a interactive session with tools container in order to install and run deCK
docker exec -it tools sh
- Install decK in the tools container:
cd /tmp
curl -sL https://github.com/Kong/deck/releases/download/v1.17.3/deck_1.17.3_linux_arm64.tar.gz -o deck.tar.gz
tar -xf deck.tar.gz -C /tmp
cp /tmp/deck /usr/local/bin/
- Backup. Run decK to create a backup of the configuration you have just made:
deck dump --kong-addr http://iam:8001
Now have a look at the file kong.yaml
.
- Restore (dry-run). You can run diff to show a dry-run of the changes that will be loaded into IAM:
deck diff --kong-addr http://iam:8001
- Restore. To load the changes you have exported previously into
kong.yaml
run the following:
deck sync --kong-addr http://iam:8001
- Modify your local hosts file
Add a line to resolve
keycloak
to 127.0.0.1
127.0.0.1 keycloak
You can find your hosts file in:
O.S. | File |
---|---|
MacOS | /private/etc/hosts |
Windows | c:\Windows\System32\Drivers\etc\hosts |
Run Keycloak as your Identity Provider:
cd keycloak
docker-compose up -d
- Access your local Keycloak in: https://localhost:7443
- Login into Administration Console using
admin
/test
We are going to use the default "master" realm.
Create a client that will represent IAM (API Manager).
Click on Clients
Click on Create
Create a new client:
- Client ID:
iam
- Click Save
Edit iam
client:
- Access Type:
confidential
- Service Accounts Enabled:
On
- Root URL:
https://iam:8443
- Valid Redirect URIs:
/oidc-route/*
Click on Credentials tab and copy Secret:
Create a user that you will use to login when using your API:
Click on Users and Add User:
Enter the following:
- Name:
test
- Click Save
Click on Credentials tab and set a new password:
- Temporary:
Off
- Add a service
curl -X POST --url http://iam:8001/services/ \
--data 'name=oidc-service' \
--data 'url=http://irisA:52773/rest/GenericService/anything' | jq
- Add a route
curl -X POST --url http://iam:8001/services/oidc-service/routes \
--data 'paths[]=/oidc-route' \
--data 'name=oidc-route' \
--data 'methods[]=GET'| jq
- Test the route by opening in your browser https://iam:8443/oidc-route/somedata
- Add OpenId Connect Plugin to the route
IAM > Routes > oidc-route > Add Plugin > OpenID Connect
-
Client ID:
iam
-
Client Secret:
-
Issuer (Discovery Document URI):
https://keycloak:7443/auth/realms/master/.well-known/openid-configuration
-
Test the route again, now it should prompt a Keycloack Login Page where you can login with the user you created and finally access the API: https://iam:8443/oidc-route/somedata