Programmatic steps to request access to a Dark Service API deployed in MuleSoft Anypoint Platform and zitified by OpenZiti — NetFoundry.io
Dark Services are, in my opinion, one of the best ways (if not the best) to protect your public and internal APIs but still make them available to public and/or internal consumers.
In one of my previous articles (https://medium.com/another-integration-blog/how-to-create-apis-as-dark-services-using-mulesoft-with-netfoundry-io-20de63cde426), I’ve described how to deploy a Dark Service API into MuleSoft Runtime which is running within an Oracle Cloud Infrastructure private VCN and Subnet with no public access at all, with zero inbound communication from the internet or any other network. But, again, still consumable on the internet. (If this is your first contact with Dark Services and you feel that this idea makes no sense at all, please take a look at the mentioned article).
API Portals are a very good way for interested developers to access an organization’s list of APIs and ultimately consume them. But since we are talking about Dark Services, and for consumers to get access to them they need to possess a strong identity (take a look at this other article https://medium.com/another-integration-blog/mulesoft-flex-gateway-as-dark-service-with-openziti-deployed-in-oracle-cloud-infrastructure-61f4a5a80f3d) that will allow them to connect to the interested API.
The process to request access to an API needs not only to respond with a client_id and client_secret but also it has to respond with a strong identity. That will be an extension for the normal request access process that we currently have at least in MuleSoft.
Fortunately, both MuleSoft and OpenZiti / NetFoundry offer pretty much all their functionality through APIs, and that allows us to create a custom portal that will help the consumers and producers to enable the request access process with the inclusion of returning the strong identity.
In this article, I will share the list of APIs (with request/response samples) that you could use to make the Dark Service API request access process possible.
If you want to follow the steps, these are what you need:
- A 30 days free account for the MuleSoft Anypoint Platform
- A teams account for NetFoundry.io
- Create a connected app in MuleSoft that can get access to the API Manager
- Follow this article to deploy your Dark Service API: https://medium.com/another-integration-blog/how-to-create-apis-as-dark-services-using-mulesoft-with-netfoundry-io-20de63cde426
The article assumes that all those elements are in place.
Now let’s prepare the credentials that you will use to call the APIs. Let’s start with the NetFoundry API credentials. Go to the NetFoundry console and head into this section:
Once you are there, create a new API Account:
After that you will be given a set of credentials:
Then, add to the API account the following roles:
You are all set with NetFoundry.
For MuleSoft, you need to create a connected app. To do it, head into Access Management of Anypoint Platform and create a connected app:
Then click here:
You will be taken to the following screen:
Configure it as an app that acts on behalf of a user and select the following scopes:
Click Next and select your business group:
And finally the environment:
Then just click on the save button:
In the list of connected apps:
Copy the client_id and secret using those buttons (yellow).
You are all set to use both the MuleSoft and NetFoundry admin APIs to make the right calls to request access to a Dark Service API.
We are going to use the very same API that I created in this article: https://medium.com/another-integration-blog/how-to-create-apis-as-dark-services-using-mulesoft-with-netfoundry-io-20de63cde426
Here are the steps:
- Get a MuleSoft access token
- Get the MuleSoft organization id from where the consumers will look for APIs
- Get the MuleSoft environment id
- List the existing APIs of the organization
- Create a new consumer app or reuse an existing one:
a) List existing apps and retrieve their app ID
b) Create a new app to get the app ID
6. Get the Tier Id for a given API
7. Register your app to a given API for a specific tier.
8. Get a valid access token from NetFoundry.
9. Create an endpoint in NetFoundry, retrieve the JWT token and generate the strong identity file from it
Now let’s check the API calls.
1. Get access token from MuleSoft (use your client_id and secret afor the Authorization Basic header)
curl --location 'https://anypoint.mulesoft.com/accounts/api/v2/oauth2/token' \
--header 'Authorization: Basic MjExMzdjMWYzZTNlNDk23ODY3RTY=' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=manage:api_contracts' \
--data-urlencode 'username=your_user' \
--data-urlencode 'password=your_password'
This will return:
{
"access_token": "02b6c6e6-262a-82ca-0f351c41f60d",
"expires_in": 3600,
"token_type": "bearer"
}
Take note of the token since you will need it for the next calls.
Now that you have your token:
2. Get Mulesoft organization id:
curl --location 'https://anypoint.mulesoft.com/accounts/api/me' \
--header 'Authorization: Bearer 02b6c6e6-262a-82ca-0f351c41f60d' \
That will return a larger json that the following:
{
"client": {
"client_id": "b6d61cc64e2482cb17af74e34ca6",
"name": "testDarkApp",
"org_id": "ac584c40-1bf7-4c08-96a3-c9013a01d871"
},
but the relevant thing is the org_id
Now with the org_id , let’s get the environment id:
curl --location 'https://anypoint.mulesoft.com/accounts/api/organizations/ac584c40-1bf7-4c08-96a3-c9013a01d871/environments' \
--header 'Authorization: Bearer 02b6c6e6-262a-82ca-0f351c41f60d' \
That will respond:
{
"data": [
{
"id": "eedf81ec-17cbd45-8b2648afd98f",
"name": "Design",
"organizationId": "ac584c48-96a3-c9013a01d871",
"isProduction": false,
"type": "design",
"clientId": "12c471b0d99f4d64dc7c9ab5d"
},
{
"id": "86e8-46c7-a86a-1d483e4d0b3f",
"name": "Preprod",
"organizationId": "ac584c40-1bf7-6a3-c9013a01d871",
"isProduction": false,
"type": "sandbox",
"clientId": "9e2e33d5b8a84500f4866c5fe489"
},
{
"id": "c1d2045d-5ba9-40e5-b00a-a36beeb",
"name": "Production",
"organizationId": "ac584c40-1b8-96a3-c9013a01d871",
"isProduction": false,
"type": "sandbox",
"clientId": "2625445e390801e1b10c79852"
},
{
"id": "823d7ade-927a-4b2a-9f1eb06a6ce0",
"name": "Sandbox",
"organizationId": "ac584c44c08-96a3-c9013a01d871",
"isProduction": false,
"type": "sandbox",
"clientId": "f90f608aef0c43b2b24721d0ade27878"
},
{
"id": "39f5d8c8-4a3c-44c6-8109-969a0882",
"name": "SBX-SPS",
"organizationId": "ac5841bf7-4c08-96a3-c9013a01d871",
"isProduction": false,
"type": "sandbox",
"clientId": "6a2cfff424650b73a08bc20ef3baa"
}
],
"total": 5
}
Get the environment id from where your APIs are located
Now list all APIs from a given environment:
curl --location --request GET 'https://anypoint.mulesoft.com/apimanager/api/v1/organizations/ac584c40-1bf7-4c08-96a01d871/environments/39f5d8c8-4a3c-44c6-8109-969a0882/apis' \
--header 'Authorization: Bearer 02b6c6e6-262a-82ca-0f351c41f60d' \
That will return all the APIs of that given orgId and envId:
{
"total": 1,
"assets": [
{
"audit": {
"created": {
"date": "2022-12-12T21:35:44.676Z"
},
"updated": {}
},
"masterOrganizationId": "ac584c40-1bf7-4c08-96a3-3a01d871",
"organizationId": "ac584c40-1bf7-4c08-96a3-c01d871",
"id": 212785440,
"name": "groupId:ac540-1bf7-4c08-96a3-c9013a01d871:assetId:mule-dark-api",
"exchangeAssetName": "mule-dark-api",
"groupId": "ac584c40-7-4c08-96a3-c9013a01d871",
"assetId": "mule-dark-api",
"apis": [
{
"audit": {
"created": {
"date": "2022-12-12T21:35:44.676Z"
},
"updated": {
"date": "2022-12-21T21:54:30.831Z"
}
},
"masterOrganizationId": "ac584c4bf7-4c08-96a3-c9013a01d871",
"organizationId": "ac584c40-1-4c08-96a3-c9013a01d871",
"id": 18364226,
"instanceLabel": "mule-dark-api",
"groupId": "ac584c4f7-4c08-96a3-c9013a01d871",
"assetId": "mule-dark-api",
"assetVersion": "1.0.0",
"productVersion": "v1",
"description": null,
"tags": [],
"order": 1,
"providerId": "d8a3dfc8-0ea9-4022-9cf2-a0ae7a8fc589",
"deprecated": false,
"lastActiveDate": "2023-04-07T20:29:01.259Z",
"endpointUri": "http://darkside.api.com",
"environmentId": "823d7ade-927a-4b2a-9f1e-2b56b06a6ce0",
"isPublic": true,
"stage": "release",
"technology": "mule4",
"status": "active",
"deployment": {
"audit": {
"created": {},
"updated": {}
},
"applicationId": "13243892",
"targetId": "22458555",
"expectedStatus": "deployed"
},
"lastActiveDelta": 54,
"pinned": false,
"activeContractsCount": 0,
"autodiscoveryInstanceName": "v1:18364226"
}
],
"totalApis": 1,
"autodiscoveryApiName": "groupId:ac584c40-1bf7-4c08-96a3-c9013d871:assetId:mule-dark-api"
}
]
}
Take note of the id, in our case: 18364226
Now create an application:
curl --location 'https://anypoint.mulesoft.com/apiplatform/repository/v2/organizations/ac584c40-1bf7-4c08-96a9013a01d871/applications?apiVersionId=18364226' \
--header 'Authorization: Bearer 37dd5451-9441c6-8e3a-c7a240cdee9f' \
--data '{
"name": "MyApp",
"description": "My App",
"url": "https://localhost:3030",
"redirectUri": [
"https://localhost:3030"
]
}'
That will return:
{
"name": "MyApp",
"description": "MyApp",
"url": "https://localhost:3030",
"redirectUri": [
"https://localhost:3030"
],
"clientProvider": {
"providerId": null
},
"coreServicesId": "1df811aac3364a10c00478358af4",
"clientId": "1df811aac330c00478358af4",
"clientSecret": "c4D10FC4b43AB48e9186e59dca2",
"audit": {
"created": {
"date": "2023-04-07T22:04:46.018Z"
},
"updated": {}
},
"masterOrganizationId": "ac584c40-1bf7-4c08-96a3-c9013a01d871",
"id": 1749038
}
Take note of:
1. id 1749038
2. clientId
3. clientSecret
From the last response, you can get the clientID and clientSecret that you would typically use to consume the API.
Now let’s get the Tier id to which the application will get subscribed to the API:
curl --location 'https://anypoint.mulesoft.com/apimanager/api/v1/organizations/ac584c40-1bf7-4c09013a01d871/environments/823d7ade-927a-4b2a-9f1ece0/apis/18614367/tiers' \
--header 'Authorization: Bearer 37dd5451-94b2-41c6-8e3a-c7ae9f' \
That will return:
{
"total": 1,
"tiers": [
{
"audit": {
"created": {
"date": "2023-04-07T18:43:29.489Z"
},
"updated": {}
},
"masterOrganizationId": "ac584c40-1bf76a3-c9013a01d871",
"organizationId": "ac584c40-1bf796a3-c9013a01d871",
"id": 1556755,
"name": "freeTier",
"description": "freeTier",
"limits": [
{
"maximumRequests": 10,
"timePeriodInMilliseconds": 60000,
"visible": true
}
],
"status": "ACTIVE",
"autoApprove": true,
"applicationCount": 2,
"apiId": 18614367
}
]
}
Take a note of the id, in this case, 1556755
Now let’s subscribe to the API of that specific organization, environment, API id, and Tier id.
curl --location 'https://anypoint.mulesoft.com/exchange/api/v2/organizations/ac584c40-1bf7-4c08-91/applications/1749038/contracts' \
--header 'Authorization: Bearer 1aaf022d-0705-4c66-b9a4d8677ee6' \
--data '{
"apiId": "18364226",
"environmentId": "823d7ade-4b2a-9f1e-2b56b06a6ce0",
"instanceType": "api",
"requestedTierId": 1556755,
"acceptedTerms": true,
"organizationId": "ac584c40-1bf796a3-c9013a01d871",
"groupId": "ac584c40-1bf7-4c08-9013a01d871",
"assetId": "mule-dark-api",
"version": "1.0.0",
"versionGroup": "v1"
}'
Now we need to create a new Endpoint in NetFoundry and a NetFoundry operator will associate our endpoint to an AppWAN. But first, let’s create the endpoint.
Our first step in this section is to get the access_token:
curl --user 76ok853atoreahb8v:qfco1bajq0dmuvbs4cb3n9u3frdcq \
> --request POST https://netfoundry-production-xfjiye.auth.us-east-1.amazoncognito.com/oauth2/token \
> --header 'content-type: application/x-www-form-urlencoded' \
> --data 'grant_type=client_credentials&scope=https%3A%2F%2Fgateway.production.netfoundry.io%2F%2Fignore-scope'
That will return an access token that you will use in your next call.
Now create the endpoint:
curl 'https://gateway.production.netfoundry.io/core/v2/endpoints' -i -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJraWQiOiJOaTREjRicENzR3dTOFwvQlhwdjJJZVZBSlpKVUhUdHVLSWxCVT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI3Nm9rODUzYXRvcmVhaDM1NTlxdnU5b2I4diIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiaHR0cHM6XC9cL2dhdGV3YXkucHJvZHVjdGlvbi5uZXRmb3VuZHJ5LmlvXC9cL2lnbm9yZS1zY29wZSIsImF1dGhfdGltZSI6MTY4MDg5ODgyMywiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfM3VEQThiWFR6IiwiZXhwIjoxNjgwOTAyNDIzLCJpYXQiOjE2ODA4OTg4MjMsInZlcnNpb24iOjIsImp0aSI6ImMzN2Y4MmZiLWQ5NGYtNGU4YS1iN2ViLTQ2NDM5YWM1ZWUwZiIsImNsaWVudF9pZCI6Ijc2b2s4NTNhdG9yZWFoMzU1OXF2dTlvYjh2In0.csnzlRmUO7h4EUVSvq5lI4QwKXM8_4QceYlQBS0d04z22VMAjmktKu-hgHYMIr_kG_fA6rvNv0qVFh78nxYG8z1S_PPhLeOYtaczIDfuA8bVADZlmtPoJQoUEdeSdzaJw4y5y_-eZNv8omYoKHZ37AjdfYqWqI7h-25QbiZEWcZsIFsPKdhLvGxtskD1jvHTCERLXWm8U569k3EaBcTzPcBKHfsK0LubTLTHyd3s9rJgwoarCFpa_YKUWxK7sNk1BCXcurl2EEmG7mquUjbIQ7yc9NobstwLMBIWLR_kLBcgzwYzcBdBNfSR9chUJZqX9hIdexH9MWPFWGqmkYRZ7g' \
-d '{"attributes":[], "networkId": "a32b688b-d71a-4344-d4e77dbb5ff","enrollmentMethod":{"ott": true},"name": "myDarkAPIClient", "selected":false}'
That will return the following:
{
"id":"32403ac3-71f884fb-53751fa7540e",
"ownerIdentityId":"a52daa41-b4-ac63-7bbca33c928f",
"createdBy":"a52daa41-b4f2-4861-ac63-7bbca33c928f",
"createdAt":"2023-04-07T22:19:01.033980Z",
"updatedAt":"2023-04-07T22:19:01.033980Z",
"deletedBy":null,
"deletedAt":null,
"networkId":"a32b688b-d71a-4344cd4e77dbb5ff",
"zitiId":null,
"name":"myDarkAPIClient",
"typeId":null,
"appId":null,
"appVersion":null,
"branch":null,
"revision":null,
"type":null,
"version":null,
"arch":null,
"os":null,
"osRelease":null,
"osVersion":null,
"externalId":null,
"authPolicyId":null,
"disabled":false,
"disabledAt":null,
"disabledUntil":null,
"hasApiSession":false,
"hasEdgeRouterConnection":false,
"lastOnlineAt":null,
"syncId":null,
"syncResourceId":null,
"attributes":[
],
"jwtExpiresAt":null,
"mfaEnabled":false,
"jwt":"safasfasdfafs34242423422424(faf",
"online":false,
"_links":{
"network":{
"href":"https://gateway.production.netfoundry.io/core/v2/networks/a32b688b-d71a-4344-91e6-cd4e77dbb5ff",
"title":"spstechisnow",
"profile":"parent"
},
"self":{
"href":"https://gateway.production.netfoundry.io/core/v2/endpoints/32403ac3-71f8-4249-84fb-53751fa7540e",
"title":"myDarkAPIClient"
},
"process":{
"href":"https://gateway.production.netfoundry.io/core/v2/process/59d0806e-a867-4446-85f8-3736ded81fca",
"title":"Create Endpoint",
"deprecation":"Use 'execution' link instead",
"profile":"meta"
},
"process-execution":{
"href":"https://gateway.production.netfoundry.io/core/v2/process-executions/59d0806e-a867-4446-85f8-3736ded81fca",
"title":"Create Endpoint",
"deprecation":"Use 'execution' link instead",
"profile":"meta"
},
"execution":{
"href":"https://gateway.production.netfoundry.io/core/v2/executions/e3f22ce3-932d-45b3-bd28-f828483cc072",
"title":"Create Endpoint",
"profile":"meta"
}
}
}
Take note of the jwt token
The consumer will have to create the following json file with the next command to use its identity in the SDK:
ziti-edge-tunnel enroll -j myclient1.jwt -i myclient1.json
And the NetFoundry administrator will have to associate the endpoint with an AppWAN to give access to the service.
This article intends to share how to use the admin APIs for both NetFoundry and MuleSoft to illustrate the steps that an API provider should take care to give access to consumers to Dark Services.
I will publish the Postman Collection for this, so you can follow the APIS calls more easily.