Build Your Own Portal¶
The Omnistrate platform is fully API driven and allows you to fully customize the user experience of your SaaS offering. We will walk through a quick example of a DBaaS offering with basic REST APIs which you can integrate within the framework of your choice to design your SaaS portal.
Restish¶
We will use the excellent Restish to walk through the APIs and design a basic user portal for your SaaS.
Setup¶
The Omnistrate platform provides 2 separate set of API models for your use:
- APIs to operate your service
- Impersonation APIs to operate the portal on behalf of your customers
Let's setup two separate API profiles in Restish for these use-cases.
-
APIs to operate your service
-
Impersonation APIs to operate the portal on behalf of your customers
The default profile is for you as a service owner to onboard (sign-up, validate token, etc.) a customer. The rest of the APIs in this model are operated in the context of the customer.
Next, we will setup the authentication mechanism for each of these APIs.
Add your username / password to a JSON file service-signin.json
:
Sign-in as the service owner:
restish portal signin-api-signin <service-signin.json
HTTP/2.0 200 OK
Content-Length: 370
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 05:53:31 GMT
{
jwtToken: "<your-jwt-token>"
}
Now, configure both the APIs with the JWT token:
restish api edit
# Modify the JSON file to use the JWT token
{
"$schema": "https://rest.sh/schemas/apis.json",
"portal": {
"base": "https://api.omnistrate.cloud/2022-09-01-00",
"profiles": {
"default": {
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
},
"service-operator": {
"base": "https://api.omnistrate.cloud/2022-09-01-00/fleet",
"profiles": {
"default": {
"headers": {
"Authorization": "Bearer <your-jwt-token>"
}
}
}
}
}
Verify that the APIs are setup correctly by checking the caller identity:
restish portal users-api-describe-user
HTTP/2.0 200 OK
Content-Length: 849
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:02:14 GMT
{
createdAt: "2023-04-13T10:07:33Z"
email: "[email protected]"
id: "user-18ZZD6JBrS"
lastModifiedAt: "2024-10-04T21:30:15Z"
name: "Omnistrate Demo"
orgDescription: "OmnistrateDemo"
orgName: "OmnistrateDemo"
orgURL: "https://www.omnistrate.com"
planName: "ENTERPRISE"
roleType: "root"
}
Onboard your first customer¶
Now that the APIs are setup, let's onboard your first customer. Omnistrate provides an event loop to manage the lifecycle of your customers. Every customer activity (sign-up, invite, subscription request, etc.) generates an event that you can poll in your app and take appropriate action.
Use the portal
APIs to create a new customer. Add the following JSON payload to a file customer-signup.json
:
{
"companyDescription": "The best eCommerce website",
"companyUrl": "https://www.example.com",
"email": "[email protected]",
"legalCompanyName": "eCommerce",
"name": "John Doe",
"password": "<password>"
}
Use the service-operator
API to fetch the details of the sign-up event:
restish service-operator events-api-list-events
HTTP/2.0 200 OK
Content-Length: 518
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:13:03 GMT
{
events: [
{
eventID: "event-P1AlF9ECW5"
eventPayload: {
inviting_user_name: ""
invoice_id: ""
is_user_enabled: false
product_tier_name: ""
service_name: ""
token: "<validation-token>"
user_email: "[email protected]"
user_id: "user-dcvEakUjz0"
user_name: ""
user_organization: ""
}
eventType: "CustomerSignUp"
orgID: "org-4aoZZqPP3T"
orgName: "Omnistrate"
orgURL: ""
priority: ""
time: "2024-10-07T06:10:38Z"
userEmail: "[email protected]"
userID: "user-dcvEakUjz0"
userName: ""
}
]
}
Your service can send an email at this point to the customer with the validation token to complete the sign-up process.
For now, let's explicitly validate the customer with the token in the event payload. Add the following JSON payload to a file customer-validate.json
:
{
"email": "[email protected]",
"token": "<validation-token>"
}
restish portal signup-api-validate-token <customer-validate.json
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 06:16:29 GMT
Your customer is now onboarded and your portal can now impersonate the customer to perform actions on their behalf. To finish the process, let's acknowledge the event:
restish service-operator events-api-acknowledge-event event-P1AlF9ECW5
restish service-operator events-api-list-events
HTTP/2.0 200 OK
Content-Length: 14
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:38:01 GMT
{
events: []
}
Let's sign-in as the customer. Add the following JSON payload to a file customer-signin.json
:
{
"email": "[email protected]",
"password": "<password>"
}
Reconfigure the portal
API with another profile to impersonate the customer:
restish api edit
...
"portal": {
"base": "https://api.omnistrate.cloud/2022-09-01-00",
"profiles": {
"customer": {
"headers": {
"authorization": "Bearer <customer-jwt-token>"
}
},
"default": {
"headers": {
"authorization": "Bearer <your-jwt-token>"
}
}
}
}
...
Impersonate the customer and fetch the details of the customer:
restish portal users-api-describe-user --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 420
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:32:44 GMT
{
createdAt: "2024-10-07T06:10:38Z"
email: "[email protected]"
id: "user-dcvEakUjz0"
lastModifiedAt: "2024-10-07T06:16:29Z"
name: "John Doe"
orgDescription: "The best eCommerce website"
orgFavIconURL: ""
orgId: "org-z0sasy6jke"
orgLogoURL: ""
orgName: "eCommerce"
orgPrivacyPolicy: ""
orgSupportEmail: ""
orgTermsOfUse: ""
orgURL: "https://www.example.com"
planName: "STARTER_NO_COMMIT"
roleType: "root"
}
User management for your customers¶
Omnistrate provides the concept of an organization for your customers to manage their users. Once onboarded, your customers can invite other team members to join their organization on Omnistrate through the portal APIs. This provides a unified view for you and your customers to manage the billing, usage, and access control of the users of your service.
Invite a customer¶
Let's impersonate the customer we just onboarded and invite another user to join their organization. Add the following JSON payload to a file customer-invite.json
:
{
"email": "[email protected]"
}
restish portal users-api-customer-invite-user <customer-invite.json --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 06:39:21 GMT
Use the service-operator
API to fetch the details of the invite event:
restish service-operator events-api-list-events
HTTP/2.0 200 OK
Content-Length: 486
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:40:45 GMT
{
events: [
{
eventID: "event-CoSqtH5emU"
eventPayload: {
inviting_user_name: "John Doe"
invoice_id: ""
is_user_enabled: false
product_tier_name: ""
service_name: ""
token: ""
user_email: "[email protected]"
user_id: "user-qUClcuYyBW"
user_name: ""
user_organization: ""
}
eventType: "InviteUser"
orgID: "org-4aoZZqPP3T"
orgName: "Omnistrate"
orgURL: ""
priority: ""
time: "2024-10-07T06:40:35Z"
userEmail: "[email protected]"
userID: "user-qUClcuYyBW"
userName: ""
}
]
}
Similar to the onboarding event processing loop, your service can send an email to the invited user to sign-up on your portal.
Let's acknowledge the event:
restish service-operator events-api-acknowledge-event event-CoSqtH5emU
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 06:44:18 GMT
Let's manually sign the invited user up. Add the following JSON payload to a file customer-invited-signup.json
:
{
"email": "[email protected]",
"name": "Jane Smith",
"password": "<password>"
}
restish portal users-api-customer-signup <customer-invited-signup.json
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 06:43:23 GMT
Use the service-operator
API to fetch the details of the sign-up event:
restish service-operator events-api-list-events
HTTP/2.0 200 OK
Content-Length: 518
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:46:03 GMT
{
events: [
{
eventID: "event-WueVADcmGx"
eventPayload: {
inviting_user_name: ""
invoice_id: ""
is_user_enabled: false
product_tier_name: ""
service_name: ""
token: "<validation-token>"
user_email: "[email protected]"
user_id: "user-qUClcuYyBW"
user_name: ""
user_organization: ""
}
eventType: "CustomerSignUp"
orgID: "org-4aoZZqPP3T"
orgName: "Omnistrate"
orgURL: ""
priority: ""
time: "2024-10-07T06:43:23Z"
userEmail: "[email protected]"
userID: "user-qUClcuYyBW"
userName: ""
}
]
}
Validate the invited user. Add the following JSON payload to a file customer-invited-validate.json
:
{
"email": "[email protected]",
"token": "<validation-token>"
}
restish portal signup-api-validate-token <customer-invited-validate.json
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 06:47:59 GMT
Acknowledge the event:
List users in an organization¶
Let's use the customer profile to query a list of users in their organization:
restish portal users-api-describe-users-by-org --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 437
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:49:08 GMT
{
Id: "user-dcvEakUjz0"
orgUsers: [
{
email: "[email protected]"
invitedAt: "2024-10-07T06:10:38Z"
name: "John Doe"
roleType: "root"
userId: "user-dcvEakUjz0"
}
{
email: "[email protected]"
invitedAt: "2024-10-07T06:40:35Z"
name: "Jane Smith"
roleType: "member"
userId: "user-qUClcuYyBW"
}
]
}
You can see both the customer and the invited user in your customer's organization.
Subscription management for your customers¶
Now that your customers are onboarded, we need to provide a way for them to subscribe to your service plans. On the Omnistrate portal, you can define multiple services and multiple service plans for each service. A Service Plan allows you to define the tenancy (multi-tenant / dedicated), pricing, deployment model (in your cloud account or in your customer's cloud account), and the capabilities of the service. This step assumes you have already defined atleast 1 service on Omnistrate and have a Production environment ready for this service. If not, head over to https://omnistrate.cloud/services and create a new service and release a production environment for it.
List available services¶
As your customer, you can get a list of all services they can subscribe to using the Service offering APIs. Let's use the customer profile to query a list of services available for subscription:
restish portal service-offering-api-list-service-offering --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 1739
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 06:59:57 GMT
{
serviceIds: ["s-JA6hp8BqRL"]
services: [
{
createdAt: "2024-10-07T06:59:06Z"
isDeprecated: false
offerings: [
{
AutoApproveSubscription: true
awsRegions: ["us-east-2"]
cloudProviders: ["aws", "gcp"]
gcpRegions: ["us-central1"]
productTierDescription: "Postgres SaaS hosted in Omnistrate account"
productTierDocumentation: "Provide guides and informational resources for this plan"
productTierID: "pt-48SQR0HcCe"
productTierName: "Postgres SaaS"
productTierPlanDescription: "Postgres SaaS hosted in Omnistrate account"
productTierPricing: {
value: "Specify the pricing model and structure for this plan"
}
productTierSupport: "Detail the support options available for this plan"
productTierType: "OMNISTRATE_DEDICATED_TENANCY"
productTierURLKey: "postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy"
productTierVersion: "1.0"
resourceParameters: [
{
isAutoscalingEnabled: false
isBackupEnabled: false
isDeprecated: false
name: "Cluster"
resourceId: "r-M7LY75veyE"
urlKey: "cluster"
}
]
serviceAPIID: "sa-XwalJyGTbo"
serviceAPIVersion: "v1"
serviceEnvironmentID: "se-7esFfwjU0p"
serviceEnvironmentName: "Production"
serviceEnvironmentType: "PROD"
serviceEnvironmentURLKey: "production"
serviceEnvironmentVisibility: "PUBLIC"
serviceLogoURL: ""
serviceModelID: "sm-oqvlwYhRZ9"
serviceModelName: "Postgres SaaS OMNISTRATE_HOSTED"
serviceModelStatus: "READY"
serviceModelType: "OMNISTRATE_HOSTED"
serviceModelURLKey: "postgres-saas-omnistrate-hosted"
}
]
serviceDescription: "Postgres SaaS"
serviceId: "s-JA6hp8BqRL"
serviceName: "Postgres SaaS"
serviceOrgId: "org-4aoZZqPP3T"
serviceProviderId: "sp-JGcqr1hPxX"
serviceProviderName: "Omnistrate"
serviceURLKey: "postgres-saas"
}
]
}
In this example, we have a Postgres SaaS service available for subscription and ready-to-use in AWS and GCP.
Subscribe to a service¶
Let's subscribe to this service as the customer. Add the following JSON payload to a file customer-subscribe.json
using information from the listing above:
restish portal subscription-api-create-subscription <customer-subscribe.json --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 18
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:03:51 GMT
Let's fetch the details of the subscription from your customer's POV:
restish portal subscription-api-list-subscriptions --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 517
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:13:40 GMT
{
ids: ["sub-ImCvoojwpm"]
subscriptions: [
{
accountConfigIdentityId: "org-z0sasy6jke"
cloudProviderNames: ["aws", "gcp"]
createdAt: "2024-10-07T07:13:19Z"
defaultSubscription: false
id: "sub-ImCvoojwpm"
productTierId: "pt-48SQR0HcCe"
productTierName: "Postgres SaaS"
roleType: "root"
rootUserId: "user-dcvEakUjz0"
serviceId: "s-JA6hp8BqRL"
serviceLogoURL: ""
serviceName: "Postgres SaaS"
serviceOrgId: "org-4aoZZqPP3T"
serviceOrgName: "Omnistrate"
status: "ACTIVE"
subscriptionOwnerName: "John Doe"
}
]
}
Deploying a cluster of Postgres¶
Now that your customer has subscribed to the Postgres SaaS service, let's deploy a cluster of Postgres for them. The resource-instance APIs provide a way to deploy a cluster of Postgres for your customer using the subscription ID. The resource instance APIs are designed to be asynchronous and return a deployment ID that you can poll to get the status of the task. The API requires the following parameters that can be obtained from the service offering listing above:
restish resource-instance-api-create-resource-instance serviceproviderid servicekey serviceapiversion serviceenvironmentkey servicemodelkey producttierkey resourcekey <input.json
For the Postgres SaaS service, the command would look like:
restish portal resource-instance-api-create-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster <customer-instance.json --rsh-profile customer
HTTP/2.0 202 Accepted
Content-Length: 28
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:24:09 GMT
{
id: "instance-5i4m1cwrp"
}
The customer-instance.json
file should contain the configuration for the Postgres cluster. Eg:
{
"region": "us-east-2",
"cloud_provider": "aws",
"requestParams": {
"dbName": "abc",
"instanceType": "t4g.small",
"pgadminEmailAddress": "[email protected]",
"postgresqlPassword": "<password>",
"postgresqlUsername": "<username>"
}
}
Let's fetch the details of the deployment using the deployment / resource-instance ID:
restish portal resource-instance-api-describe-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster instance-5i4m1cwrp --rsh-profile customer
HTTP/2.0 200 OK
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:28:20 GMT
{
active: true
cloud_provider: "aws"
createdByUserId: "user-dcvEakUjz0"
createdByUserName: "John Doe"
created_at: "2024-10-07T07:24:08Z"
detailedNetworkTopology: {
r-DbJa2uX0L6: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "pgadmin.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [80, 443]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_NORMAL"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "pgadmin-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "pgadmin-0"
ports: [80]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "pGAdmin"
resourceName: "PGAdmin"
}
r-M7LY75veyE: {
allowedIPRanges: []
clusterEndpoint: ""
customDNSEndpoint: {
enabled: false
}
hasCompute: false
main: true
networkingType: ""
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "cluster"
resourceName: "Cluster"
}
r-WyuLUOnWVy: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "r-wyuluonwvy.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [5432]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_IDLE"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "writer-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "writer-0"
ports: [5432]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "writer"
resourceName: "Writer"
}
r-aRgU5ukzZV: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "r-argu5ukzzv.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [5432]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_IDLE"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "reader-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "reader-0"
ports: [5432]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "reader"
resourceName: "Reader"
}
r-obsrvpt48sqr0hcce: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "streamer.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [8080]
customDNSEndpoint: {
enabled: false
}
hasCompute: false
main: false
networkingType: "PUBLIC"
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "omnistrateobserv"
resourceName: "Omnistrate Observability"
}
}
highAvailability: false
id: "instance-5i4m1cwrp"
last_modified_at: "2024-10-07T07:28:02Z"
network_type: "PUBLIC"
productTierFeatures: {
LOGS: {
enabled: true
featureName: "LOGS"
label: ["ONLINE"]
scope: "CUSTOMER"
}
METRICS: {
enabled: true
featureName: "METRICS"
label: ["ONLINE"]
scope: "CUSTOMER"
}
}
region: "us-east-2"
result_params: {
dbName: "abc"
instanceType: "t4g.small"
pgadminEmailAddress: "[email protected]"
postgresqlUsername: "homepage"
}
status: "RUNNING"
subscriptionId: "sub-ImCvoojwpm"
}
This provides a detailed view of the Postgres cluster deployed for your customer including the network topology, health status, and the endpoints for the cluster. You can customize the experience for your customer by providing them with the necessary information to connect to the cluster.
Customer RBAC¶
Omnistrate allows more fine-grained RBAC within the context of a customer organization. A customer can invite other users within (or outside) their org to share a subscription with them. This provides an easy way for your customers to pay for your service while providing easy access to their team members. In this example, let's add the user we invited earlier to share the subscription with the customer that owns it.
Setup the invited user¶
First, let's login as the invited user. Add the following JSON payload to a file customer-invited-signin.json
:
{
"email": "[email protected]",
"password": "<password>"
}
restish portal users-api-customer-signin <customer-invited-signin.json
HTTP/2.0 200 OK
Content-Length: 370
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:33:17 GMT
{
jwtToken: "<user1-jwt-token>"
}
Add the invited user to the Restish profile:
restish api edit
# Modify the JSON file to use the JWT token
{
"$schema": "https://rest.sh/schemas/apis.json",
"portal": {
"base": "https://api.omnistrate.cloud/2022-09-01-00",
"profiles": {
"customer": {
"headers": {
"authorization": "Bearer <customer-jwt-token>"
}
},
"default": {
"headers": {
"authorization": "Bearer <your-jwt-token>"
}
},
"user1": {
"headers": {
"authorization": "Bearer <user1-jwt-token>"
}
}
}
}
}
Let's verify the new user's identity:
restish portal users-api-describe-user --rsh-profile user1
HTTP/2.0 200 OK
Content-Length: 424
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:34:52 GMT
{
createdAt: "2024-10-07T06:40:35Z"
email: "[email protected]"
id: "user-qUClcuYyBW"
lastModifiedAt: "2024-10-07T06:47:59Z"
name: "Jane Smith"
orgDescription: "The best eCommerce website"
orgFavIconURL: ""
orgId: "org-z0sasy6jke"
orgLogoURL: ""
orgName: "eCommerce"
orgPrivacyPolicy: ""
orgSupportEmail: ""
orgTermsOfUse: ""
orgURL: "https://www.example.com"
planName: "STARTER_NO_COMMIT"
roleType: "member"
}
Share the subscription with the invited user¶
Let's attempt to describe the Postgres cluster as the invited user:
restish portal resource-instance-api-describe-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster instance-5i4m1cwrp --rsh-profile user1
HTTP/2.0 403 Forbidden
Content-Length: 157
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:36:13 GMT
{
fault: false
id: ""
message: "unauthorized: not authorized to perform DESCRIBE action on instance"
name: "forbidden"
temporary: false
timeout: false
}
As expected, the invited user does not have the necessary permissions to describe the Postgres cluster.
Let's add the user to the subscription sub-ImCvoojwpm
with read-only rights. Add the following JSON payload to a file customer-invite-user.json
:
{
"email": "[email protected]",
"roleType": "reader"
}
restish portal consumption-user-api-invite-user sub-ImCvoojwpm <subscription-invite.json --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 07:40:42 GMT
Now let's try to describe the Postgres cluster as the invited user:
restish portal resource-instance-api-describe-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster instance-5i4m1cwrp --rsh-profile user1
HTTP/2.0 200 OK
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:41:42 GMT
{
active: true
cloud_provider: "aws"
createdByUserId: "user-dcvEakUjz0"
createdByUserName: "John Doe"
created_at: "2024-10-07T07:24:08Z"
detailedNetworkTopology: {
r-DbJa2uX0L6: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "pgadmin.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [80, 443]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_IDLE"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "pgadmin-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "pgadmin-0"
ports: [80]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "pGAdmin"
resourceName: "PGAdmin"
}
r-M7LY75veyE: {
allowedIPRanges: []
clusterEndpoint: ""
customDNSEndpoint: {
enabled: false
}
hasCompute: false
main: true
networkingType: ""
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "cluster"
resourceName: "Cluster"
}
r-WyuLUOnWVy: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "r-wyuluonwvy.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [5432]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_IDLE"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "writer-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "writer-0"
ports: [5432]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "writer"
resourceName: "Writer"
}
r-aRgU5ukzZV: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "r-argu5ukzzv.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [5432]
customDNSEndpoint: {
enabled: false
}
hasCompute: true
main: false
networkingType: "PUBLIC"
nodes: [
{
availabilityZone: "us-east-2a"
detailedHealth: {
ConnectivityStatus: "HEALTHY"
DiskHealth: "HEALTHY"
LoadHealth: "POD_IDLE"
NodeHealth: "HEALTHY"
ProcessHealth: "HEALTHY"
ProcessLiveness: "HEALTHY"
}
endpoint: "reader-0.instance-5i4m1cwrp.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
healthStatus: "HEALTHY"
id: "reader-0"
ports: [5432]
status: "RUNNING"
storageSize: 30
}
]
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "reader"
resourceName: "Reader"
}
r-obsrvpt48sqr0hcce: {
allowedIPRanges: ["0.0.0.0/0"]
clusterEndpoint: "streamer.hc-pelsk80ph.us-east-2.aws.f2e0a955bb84.cloud"
clusterPorts: [8080]
customDNSEndpoint: {
enabled: false
}
hasCompute: false
main: false
networkingType: "PUBLIC"
privateNetworkCIDR: "172.0.0.0/16"
publiclyAccessible: true
resourceInstanceMetadata: null
resourceKey: "omnistrateobserv"
resourceName: "Omnistrate Observability"
}
}
highAvailability: false
id: "instance-5i4m1cwrp"
last_modified_at: "2024-10-07T07:28:02Z"
network_type: "PUBLIC"
productTierFeatures: {
LOGS: {
enabled: true
featureName: "LOGS"
label: ["ONLINE"]
scope: "CUSTOMER"
}
METRICS: {
enabled: true
featureName: "METRICS"
label: ["ONLINE"]
scope: "CUSTOMER"
}
}
region: "us-east-2"
result_params: {
dbName: "abc"
instanceType: "t4g.small"
pgadminEmailAddress: "[email protected]"
postgresqlUsername: "homepage"
}
status: "RUNNING"
subscriptionId: "sub-ImCvoojwpm"
}
Let's try to delete the Postgres cluster as the invited user:
restish portal resource-instance-api-delete-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster instance-5i4m1cwrp --rsh-profile user1
HTTP/2.0 403 Forbidden
Content-Length: 155
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:43:10 GMT
{
fault: false
id: ""
message: "unauthorized: not authorized to perform DELETE action on instance"
name: "forbidden"
temporary: false
timeout: false
}
As expected, the invited user does not have the necessary permissions to delete the Postgres cluster.
Revoke access to the subscription¶
To complete the RBAC demonstration, let's revoke the user's access to the subscription:
restish portal consumption-user-api-revoke-user-role sub-ImCvoojwpm <subscription-invite.json --rsh-profile customer
HTTP/2.0 200 OK
Content-Length: 0
Content-Security-Policy: default-src 'none'
Date: Mon, 07 Oct 2024 07:44:22 GMT
restish portal resource-instance-api-describe-resource-instance --subscription-id sub-ImCvoojwpm sp-JGcqr1hPxX postgres-saas v1 production postgres-saas-omnistrate-hosted postgres-saas-postgres-saas-omnistrate-hosted-model-omnistrate-dedicated-tenancy cluster instance-5i4m1cwrp --rsh-profile user1
HTTP/2.0 403 Forbidden
Content-Length: 157
Content-Security-Policy: default-src 'none'
Content-Type: application/json
Date: Mon, 07 Oct 2024 07:44:44 GMT
{
fault: false
id: ""
message: "unauthorized: not authorized to perform DESCRIBE action on instance"
name: "forbidden"
temporary: false
timeout: false
}