Back

Intelligent Access for custom apps: getting started with Veza’s Open Authorization API

Where your traditional identity system stops providing access information at the role level, you are often left with fetching the remaining fine-grained authorization data separately from each native system the identities have access to. The trend towards centralizing access around Identity Management Providers, and towards more and more connected systems has multiplied this flaw, and created a new surface area for identity-based cyber threats. This is why we created the Veza Access Graph, so that you can answer in real-time: who can take what action on what data?

At the core of Veza is a data pipeline platform that facilitates integrations with all the systems in your enterprise – which include identity sources (e.g. IdPs like AD and Okta, HRIS systems like Workday, etc.), data platforms (e.g. Snowflake, RDMBS, NoSQL databases, etc.) SaaS applications, on-prem apps and more – to create a rich dataset showing connections between identities, authorization metadata and resources. The data model is essentially a graph, which enables you to easily visualize and identify which users have what access to which resources.

Open Authorization API

A growing catalog of built-in integrations is provided out-of-the-box, where only configuration necessary for Veza to start extracting metadata; Integrations are done in minutes, not weeks or months. Any system can be brought in to the graph, not just out-of-the-box integrations. To support custom applications, and systems that aren’t native integrations yet, or those that require custom modeling, Veza provides the Open Authorization API (OAA).

The following diagram summarizes the process of leveraging OAA to import custom resources and their authorization metadata:

To integrate a custom system, you will use APIs or any appropriate method to list identities and resources, and retrieve entity and authorization metadata such as permissions, roles, and activity status. All this information is formatted into a standardized JSON model, and pushed to Veza via the OAA REST API. Developers can leverage the Python SDK to format and submit the data, or work directly with the JSON templates and REST API using any language of choice.

Goals

In this tutorial, you will learn how to use the Python SDK to build an OAA connector to PagerDuty.

Note: A native PagerDuty integration already exists in Veza, but the purpose here is to walk through building a connector from scratch with an actual application, to give readers a feel of the process.

The goals of this tutorial are:

  • Quickly get familiar with the concepts and tools
  • How to map an application’s authorization model into Veza’s Custom Application Template.
  • Best practices

Prerequisites

  • Python >=3.8
  • An API Key to your Veza tenant
  • A PagerDuty test API token. You can grab it from the top of the api-reference page here. Optionally, you can obtain one from an existing PagerDuty account your organization owns, or if you need your own PagerDuty account to test with, you can sign up for a free developer account here.

Step 1: Modeling your application

The first step in writing your own connector is figuring out how to map you app’s authorization model to Veza’s Custom Application Template, shown below:

The first thing to notice is that this is a graph: The model is made up of nodes (representing types of entities: Local Users, Local Groups, Local Roles, Permissions and Resources) and edges (associations between the nodes). One normally reads the graph from left to right; Local* users on the left, traversing via relations all the way to the resources they have access to, on the far right.

** We use the terminology “local” to signify that these are the applications’ native entities, which can be related to federated identities and entities (such as users and groups mastered by IdPs, Active Directories/LDAPs etc.), which are external to these systems.*

The second important fact about the model is that the entities between the users and resources are all optional, though they are more than often present in most applications.

  • Local Groups and Roles – There can be one or the other, both, or none in your model.
  • All the edges going into Permissions are optional, i.e.
    • Local Users can be directly granted Permissions, or belong to Local Groups/Roles that have permissions, or both.
    • Local Users and/or Groups may have roles
    • Any combination of the above is possible

In Veza, permissions are normalized. We call them Effective Permissions: Create, Read, Update, Delete, Metadata read/write, Non-data access, and Uncategorized. Each of the app’s specific permission are mapped to a list of Effective Permissions. For example, the permission “Foo” –maps to→ [“Read”, “Write”, “Delete”], “Bar” -maps to→ [”Read”, “Metadata read”, “Non-data”], etc.

Resources can be anything you want to track access to. For example, a fileshare app may want to list each folder as resource, or individual files; A database may want to list tables and views as resources, etc.

PagerDuty Model

Modeling PagerDuty is pretty straightforward – In PagerDuty, there are Users and Roles (and associated permissions); And there are Teams. Thus, in Veza’s Custom Application Template, these are Local Users, Local Roles and Local Groups.

What about resources? From PagerDuty’s documentation, Teams allow you to “group and control user access to associated PagerDuty objects, such as escalation policies, users, schedules and services.” Teams also allow you to “customize the PagerDuty web app by filtering for information relevant to their Team’s associated objects.” So a Team is like a Resource – something a user has privileged access to based on their role.

The following table summarizes how we’ll map entities between PagerDuty and Veza:

PagerDutyVeza Custom Application Template
UserLocal User
RoleLocal Role
TeamLocal Group
TeamResource (type = “team”)

Let’s start coding

Now that we’ve determined our model, let’s start coding the integration.

To see the full completed solution, refer to this Github link.

Start your virtual environment:

> python3 -m venv venv
> source venv/bin/activate 

Install the OAA client:

> pip install oaaclient

Start a new file oaa_pagerduty.py with this outline:

from oaaclient.client import OAAClient, OAAClientError
from oaaclient.templates import CustomApplication, OAAPropertyType, OAAPermission
import os, sys
import requests

veza_url = os.getenv('VEZA_URL')
veza_api_key = os.getenv('VEZA_API_KEY')

def main():
    app = CustomApplication(name='Sample App', application_type='PagerDuty')

    # Define Custom Properties

    # Add Application specific permissions

    # Add Local Roles

    # Add Local Users
    
    # Add Local Groups
        
		# Add Resources (Map PagerDuty Teams to Veza Custom Application Teamplate Resources)

		# Assign local roles to users

    # Connect to the API to Push to Veza
    provider_name = 'Sample-PagerDuty'
    veza_con = OAAClient(url=veza_url, api_key=veza_api_key)
    provider = veza_con.get_provider(provider_name)
    if not provider:
        provider = veza_con.create_provider(provider_name, 'application')
    try:
        response = veza_con.push_application(provider_name,
                                             data_source_name=f'{app.name} ({app.application_type})',
                                             application_object=app,
                                             save_json=False
                                             )
        if response.get('warnings', None):
            # Veza may return warnings on a successful uploads. These are informational warnings that did not stop the processing
            # of the OAA data but may be important. Specifically identities that cannot be resolved will be returned here.
            print('-- Push succeeded with warnings:')
            for e in response['warnings']:
                print(f'  - {e}')
    except OAAClientError as e:
        print(f'-- Error: {e.error}: {e.message} ({e.status_code})', file=sys.stderr)
        if hasattr(e, 'details'):
            for d in e.details:
                print(f'  -- {d}', file=sys.stderr)
    return

if __name__ == '__main__':
    main()

Step 2: Custom Properties

Veza entity types have built-in properties, such as last_login, created_at, is_active (for Users), etc. Custom properties can also be stored on entities but need to be defined beforehand. So the next step in writing a custom connector is figuring out what custom properties you’d like to bring into the authorization graph.

For PagerDuty, we are going to add the email and is_billed custom properties to Users; and pagerduty_id, summary and default_role properties to Resources. Add the following lines of code below the # Define Custom Properties comment:

# Define Custom Properties
app.property_definitions.define_local_user_property('email', OAAPropertyType.STRING)
app.property_definitions.define_local_user_property('is_billed', OAAPropertyType.BOOLEAN)
app.property_definitions.define_resource_property('team', 'pagerduty_id', OAAPropertyType.STRING)
app.property_definitions.define_resource_property('team', 'summary', OAAPropertyType.STRING)
app.property_definitions.define_resource_property('team', 'default_role', OAAPropertyType.STRING)

Step 3: Roles and Permissions

The next step is to enumerate the list of “permissions” the app supports. For PagerDuty, we refer to the roles documentation and find that 10 roles exist, each with a set of specific permissions. Map them into the application template using this block of code:

# Add Application specific permissions
app.add_custom_permission('admin', permissions=[OAAPermission.DataWrite, OAAPermission.DataRead, OAAPermission.DataDelete, OAAPermission.MetadataRead, OAAPermission.MetadataWrite])
app.add_custom_permission('limited_user', permissions=[OAAPermission.DataRead, OAAPermission.MetadataRead])
app.add_custom_permission('manager', permissions=[OAAPermission.DataWrite, OAAPermission.DataRead, OAAPermission.MetadataWrite, OAAPermission.MetadataRead])
app.add_custom_permission('observer', permissions=[OAAPermission.DataRead, OAAPermission.MetadataRead])
app.add_custom_permission('owner', permissions=[OAAPermission.DataWrite, OAAPermission.DataRead, OAAPermission.DataDelete, OAAPermission.MetadataRead, OAAPermission.MetadataWrite])
app.add_custom_permission('read_only_limited_user', permissions=[OAAPermission.MetadataRead])
app.add_custom_permission('read_only_user', permissions=[OAAPermission.DataRead, OAAPermission.MetadataRead])
app.add_custom_permission('responder', permissions=[OAAPermission.DataWrite, OAAPermission.DataRead])
app.add_custom_permission('restricted_access', permissions=[OAAPermission.MetadataRead, OAAPermission.MetadataWrite])
app.add_custom_permission('user', permissions=[OAAPermission.DataRead, OAAPermission.MetadataRead])

# Add Local Roles
app.add_local_role('Golbal Admin', unique_id='admin', permissions=['admin'])
app.add_local_role('Responder', unique_id='limited_user', permissions=['limited_user'])
app.add_local_role('Observer', unique_id='observer', permissions=['observer'])
app.add_local_role('Account Owner', unique_id='owner', permissions=['owner'])
app.add_local_role('Limited Stakeholder', unique_id='read_only_limited_user', permissions=['read_only_limited_user'])
app.add_local_role('Full Stakeholder', unique_id='read_only_user', permissions=['read_only_user'])
app.add_local_role('Restricted Access', unique_id='restricted_access', permissions=['restricted_access'])
app.add_local_role('User', unique_id='user', permissions=['user'])
app.add_local_role('Responder', unique_id='responder', permissions=['responder'])
app.add_local_role('Manager', unique_id='manager', permissions=['manager'])

Next steps

Thus far, you’ve completed the initial steps by instantiating the app, defining custom properties, and adding the roles and permissions. In the next steps, you will read in the users, groups and resources using PagerDuty’s API and populate the app with them.

Add Local Users

Use PagerDuty’s GET /users API to list all users. Then use the add_local_user method to add Users to the application. The parameter unique_id is optional, and if omitted, name is used as the user Id. identities is used to link the user to identity sources (e.g. Okta, AD, etc.) in the graph. Mapping rules are configured within Veza to determine how identities are mapped. Normally, it would be something unique about the user, such as email.

# Add Local Users
headers = {
    'Accept': 'application/json',
    'Authorization': f'Token token={os.getenv('PAGERDUTY_API_KEY')}'
}
response = requests.get('<https://api.pagerduty.com/users?limit=100>', headers=headers)
response = response.json()
for user in response['users']:
    # Add local user. Link the local user to an IdP using email
    new_user = app.add_local_user(user.get('name'), 
                                  unique_id=user.get('id'), 
	                                identities=[user.get('email')])
    # Populate custom properties
    new_user.set_property('email', user.get('email'))
    new_user.set_property('is_billed', user.get('billed'))
    # The user's default role
    new_user.add_role(user.get('role'), apply_to_application=True)

Once a user is added, set their custom properties using set_property. Finally, set the user’s default PagerDuty role using add_role.

Add Local Groups

Next, add Local Groups. Since PagerDuty teams are mapped to Local Groups, use the GET /teams API to fetch teams and then use the add_local_group method to add Local Groups.

# Add Local Groups
response = requests.get('<https://api.pagerduty.com/teams?limit=100>', headers=headers)
teams = response.json()['teams']
for team in teams:
    app.add_local_group(team.get('name'), unique_id=team.get("id"))

Add Resources

Teams are also Resources. Use the add_resource method to add Resources to the Custom Application Template. Also populate the built in property “description,” and the custom properties we defined earlier.

# Add Resources (Map PagerDuty Teams to Veza Custom Application Teamplate Resources)
for team in teams:
    # Add local resource
	  resource = app.add_resource(team.get('name'), resource_type='team')
    # Populate built-in property description (max 1,024 char)
    resource.description = team.get('description')[:255] if team.get('description') else None
	  # Populate Custom Properties
    resource.set_property('pagerduty_id', team.get('id'))
    resource.set_property('summary', team.get('summary'))
    resource.set_property('default_role', team.get('default_role'))

Map Users to Roles to Resources

Veza application templates support binding users to application-level permissions directly, or users to roles (which is commonly found in apps that support RBAC). For PagerDuty, we model the latter (bind users to roles). In RBAC, roles also need to be associated with resources they have access to.

Use the GET /teams/{id}/members API to fetch information about which users belong in which teams (i.e. which users have access to which resources). Use the local users’ instance method add_role to bind Users to their Roles, and also associate Roles to Resources. Finally, add the Local User to the Local Group using the users’ instance method add_group.

# Assign local roles to users
for team in app.resources:
    resource = app.resources[team]
    team_id = resource.properties.get('pagerduty_id')
    response = requests.get(f'<https://api.pagerduty.com/teams/{team_id}/members>',
                            headers=headers)
    response = response.json()
    for member in response['members']:
        app.local_users[member['user']['id']].add_role(member['role'], resources=[resource])

        # Add local user to local group
        app.local_users[member['user']['id']].add_group(team_id)

Inspect your work

At this stage your code should be complete. To reference what it should look like, follow this link to the sample solution on Github.

Finally, set the environment variables VEZA_URL, VEZA_API_KEY and PAGERDUTY_API_KEY and run the program to ingest PagerDuty into your Veza tenant:

python oaa_tutorial_pagerduty.py

Congratulations! You have just written your first custom OAA integration!

Connect all systems and identities

Although Veza already has a rich catalog of built-in integrations – well over 200 and growing rapidly – we’ve enabled organizations to bring in their own custom applications into the authorization graph. The true power of the graph is its ability to link all identities to all resources in your environment, including custom ones. A comprehensive and complete authorization graph allows you to finally answer: Who can take what action on what resource? And this helps you and your organization identify risky profiles and enforce segregation of duties; Overprivileged and misconfigured permissions are easily visualized. An event driven architecture beneath this triggers alerts, notifications and automated remediation actions whenever new data arrives or changes the graph results. From these fundamental sets of capabilities, we enable access search, access intelligence, access monitoring, access workflows, lifecycle management, and access requests. And our extensible APIs allow integrations with other security platforms, enabling you to customize and automate use-cases that are just right for your organization.

Visit our Github repo to check out more OAA examples, documentation, and cool developer tools to help you work with the Veza API. We’re constantly adding content, so check in often to see what’s new, contribute and be part of our developer community.

Table of Contents