Skip to content

InterSystems IRIS + REST development + API manager workshop

License

Notifications You must be signed in to change notification settings

intersystems-ib/workshop-rest-iam

Repository files navigation

Workshop: REST and InterSystems API Manager

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.

What do you need to install?

Setup

Get InterSystems IRIS & IAM image

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.

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

IAM enabled IRIS license

IMPORTANT! Copy your InterSystems IRIS IAM enabled license file into the workshop root and rename it to iris.key.

Build IRIS image

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

Scenario: Develop a REST API

Run IRIS container

  • Run the containers we will use in this section and check you access them:
docker-compose up -d irisA tools

OpenAPI specification

Data classes and %JSON.Adaptor

  • 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.

Generate API from OpenAPI specifications

  • 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

Implement REST API methods

  • Using VS Code, complete the code of the following methods in Webinar.API.Leaderboard.v1.impl.

addPlayer

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
}

getPlayers

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
}

getPlayerById

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
}

updatePlayer

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
}

deletePlayer

ClassMethod deletePlayer(playerId As %Integer) As %DynamicObject
{
    set sc = ##class(Webinar.Data.Player).%DeleteId(playerId)
    if $$$ISERR(sc) {
    	do ..%SetStatusCode(404)
    }
    quit ""
}

Test the API

(Optional) Make your REST API part of an interoperability production

You'll plug the API implementation into a production, so you could use any feature of interoperability productions withing a REST API implementation.

Create production and add a dummy BO

  • Using the Management Portal Interoperability > List > Productions > New, create a new production called Webinar.Production.
  • Click on Production Settings and in Settings tab make sure Testing 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
  • Test the Business Operation in the Actions tab clicking on the Test button.

Make your REST API a Business Service

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 the GET Player request in Postman and check it's OK.

Scenario: Use API Manager to manage your APIs

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 API to API Manager

  • 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

Add request transformer plugin to service

  • 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.

Enable authentication (key-auth)

  • 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.

Consumers

  • 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.

Rate Limiting

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.

Developer Portal

  • 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 set File Type=spec and File Path=leaderboard.yaml.
  • Copy the content of leaderboard-api-v1.yaml.

API credentials and developers

  • 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 the api-key header by the actual credential you have just created.
  • Access the APIs documentation in Documentation.

Auditing

  • 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.

Scenario: API Manager Load Balancing

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 the Node property in the response body.

Scenario: Route by Header in API Manager

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 header version:
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 different version header request values.

Managing API Manager configuration using DeCK

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.

https://docs.konghq.com/deck/

  • 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

Scenario: Using an OIDC Identity Provider in your APIs

Setup

  • 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 (OIDC Identity Provider)

Run Keycloak as your Identity Provider:

cd keycloak
docker-compose up -d

Login to Keycloak Administration Console

We are going to use the default "master" realm.

Create a client in KeyCloak

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

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 service / route to API Manager

  • 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

Add OIDC plugin to your route

  • 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

About

InterSystems IRIS + REST development + API manager workshop

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published