Skip to main content

Permissions Model

VAMS implements a defense-in-depth authorization system using a two-tier model powered by Casbin, an open-source Attribute-Based Access Control (ABAC) and Role-Based Access Control (RBAC) policy engine. Both tiers must independently allow an operation for it to succeed -- if either tier denies access, the request is rejected.

Two-tier authorization

Every API request passes through two authorization checks before the underlying operation executes.

Tier 1 -- API route authorization

Tier 1 determines whether the user's role is allowed to call the specific API endpoint. It checks the HTTP method (GET, PUT, POST, DELETE) against the route path. This tier uses two object types:

Object TypePurposeConstraint Field
apiControls access to backend API routes (data operations).route__path
webControls access to frontend UI pages (navigation visibility).route__path
Web routes control visibility only

Web route constraints control which pages appear in the navigation menu. They do not enforce data access -- a user who knows the API endpoint could still call it if the api constraint allows. Always pair web constraints with matching api constraints.

Tier 2 -- Object-level authorization

Tier 2 determines whether the user's role is allowed to perform the specific operation on the specific data entity. It checks the HTTP method against the entity's attributes (such as databaseId, assetName, tags). This tier uses the data object types listed in the Object types and constraint fields section.

Core concepts

Users

A user is identified by their username from the authentication provider (Amazon Cognito or an external OAuth provider). Users are authenticated before any authorization logic runs.

Roles

A role is a named permission group. Users are assigned to roles, and roles have constraints associated with them. A user can belong to multiple roles, and a role can have multiple constraints.

RoleDescription
admin (default)Full CRUD access across all object types and databases. Deployed automatically.
basicReadOnly (default)Read-only access across all databases. Deployed automatically.
Custom rolesCreated by administrators to implement organization-specific access patterns.
MFA-aware roles

Roles can be configured with mfaRequired: true. When MFA is required, the role's constraints are only active when the user's session includes a valid MFA claim. If MFA is not present, the role is treated as if it does not exist for that session.

Constraints

A constraint is a policy rule that defines what a role can do. Each constraint specifies:

  • Object type -- The kind of resource the constraint applies to (for example, asset, pipeline, database).
  • Criteria -- Conditions that must be met for the constraint to match (for example, databaseId equals my-project-db).
  • Permissions -- The HTTP methods allowed or denied (GET, PUT, POST, DELETE).
  • Permission type -- Whether the constraint is an allow or deny rule.

Constraints use criteriaAnd (all conditions must match) and criteriaOr (at least one condition must match) to build complex matching rules.

Casbin policy model

VAMS stores its authorization policy in Amazon DynamoDB and uses the Casbin engine to make enforcement decisions at runtime. The policy model defines four components:

  • Request definition -- Each authorization request contains a subject (user), an object (the resource being accessed), and an action (the HTTP method).
  • Policy definition -- Each policy rule contains a subject pattern, an object matching rule, an action, and an effect (allow or deny).
  • Role definition -- Users are grouped into roles using a role inheritance model.
  • Policy effect -- The critical evaluation rule: at least one allow must match, AND no deny can match. This means deny rules always take precedence over allow rules, similar to AWS Identity and Access Management (IAM) policy evaluation.

The matchers component evaluates whether the requesting user belongs to the policy's role, whether the object matches the policy's rule expression, and whether the action matches.

Object types and constraint fields

Each object type supports specific constraint fields that can be used in criteria conditions.

Object TypeConstraint FieldsDescription
apiroute__pathBackend API route paths.
webroute__pathFrontend UI page routes.
databasedatabaseIdDatabase entity operations.
assetdatabaseId, assetName, assetType, tagsAsset entity operations (includes file operations).
pipelinedatabaseId, pipelineId, pipelineType, pipelineExecutionTypePipeline management and execution.
workflowdatabaseId, workflowIdWorkflow management and execution.
metadataSchemadatabaseId, metadataSchemaName, metadataSchemaEntityTypeMetadata schema management.
tagtagNameTag CRUD operations.
tagTypetagTypeNameTag type CRUD operations.
roleroleNameRole management.
userRoleroleName, userIdUser-to-role assignment management.

Constraint criteria operators

Criteria conditions use operators to match field values. All operators use regular expression matching internally. Criteria values are auto-escaped before being passed to the Casbin policy engine.

OperatorBehaviorInternal Regex PatternExample
equalsExact match.regexMatch(^value$)databaseId equals my-project-db
containsValue appears anywhere in the field.regexMatch(.*value.*)tags contains locked
does_not_containValue does not appear in the field.!regexMatch(.*value.*)tags does_not_contain restricted
starts_withField begins with the value.regexMatch(^value.*)databaseId starts_with team-alpha-
ends_withField ends with the value.regexMatch(.*value$)assetName ends_with .e57
is_one_ofReserved for future metadata field checks.value in r.obj.field--
is_not_one_ofReserved for future metadata field checks.!(value in r.obj.field)--
Wildcard matching

Since operators use regex internally, you can use patterns like .* for broad matching. For example, databaseId contains .* matches any database. However, prefer specific values over wildcards to follow the principle of least privilege.

The GLOBAL keyword

Pipelines, workflows, and metadata schemas support a special GLOBAL keyword for their databaseId field. GLOBAL entities are not tied to any specific database and are available across all databases.

When granting access to GLOBAL resources, always use the equals operator with the value GLOBAL. Do not use a wildcard pattern, as this could inadvertently grant access to resources in other databases.

{
"criteriaAnd": [{ "field": "databaseId", "operator": "equals", "value": "GLOBAL" }]
}

For roles scoped to a specific database, you typically need two constraints per entity type (pipeline, workflow, metadataSchema) -- one for the specific database and one for GLOBAL -- to ensure users can access both database-specific and shared resources.

Allow and deny effects

Allow rules

Allow rules grant access to specific operations. At least one allow rule must match for the operation to proceed. If no allow rules match, the operation is denied by default (implicit deny).

Deny rules

Deny rules explicitly block specific operations. A single matching deny rule overrides all allow rules for that operation. Deny rules are typically used to create exceptions within broad allow policies.

Example: Deny modification of tagged assets

{
"name": "deny-locked-assets",
"objectType": "asset",
"criteriaAnd": [{ "field": "tags", "operator": "contains", "value": "locked" }],
"groupPermissions": [
{ "permission": "PUT", "permissionType": "deny" },
{ "permission": "POST", "permissionType": "deny" },
{ "permission": "DELETE", "permissionType": "deny" }
]
}

This constraint denies all write operations on any asset tagged with locked, regardless of other allow rules. Users can still view (GET) the asset.

Common role patterns

For detailed constraint tables, JSON examples, and design rationale for common roles (Database Admin, Database User, Database Read-Only, Global Read-Only, and Deny by Tag), see Developer Guide: Permissions.

Archive versus permanent delete

The Database User role demonstrates a key pattern: the asset entity constraint grants DELETE (needed for archive operations), but the api route constraint uses the contains operator to only match paths containing archiveAsset or archiveFile. This blocks permanent delete paths (deleteAsset, deleteFile) at Tier 1 while allowing soft delete at Tier 2.

Common pitfalls

Incomplete constraint matrix

A common mistake is creating a database constraint and assuming it automatically restricts all resources within that database. The database object type only controls the database entity itself. You must create separate constraints for asset, pipeline, workflow, and metadataSchema to restrict resources within the database. See the Developer Guide: Permissions for complete constraint matrices.

Additional pitfalls to avoid:

  • Missing API route constraints -- Without Tier 1 api constraints, the user cannot call any endpoints, even if Tier 2 data constraints exist.
  • Missing web route constraints -- Without web constraints, the UI hides pages from the user (though API access still works if configured).
  • Using criteriaAnd for multiple databases -- If you need access to multiple databases, use criteriaOr (not criteriaAnd). A single entity can only have one databaseId, so multiple equals conditions in criteriaAnd will never match simultaneously.
  • Forgetting non-mutating POST routes for read-only roles -- Routes like /search and /auth/routes use POST but do not modify data. Read-only roles must allow POST on these specific paths for the UI to function.
  • Using wildcards for GLOBAL access -- When granting access to GLOBAL resources, use databaseId equals GLOBAL (not databaseId contains .*). A wildcard inadvertently matches all databases.

Permission templates

VAMS provides pre-built JSON templates for common permission profiles (Database Admin, Database User, Database Read-Only, Global Read-Only, and Deny Tagged Assets). Templates automate the creation of the full constraint matrix and support variable substitution for database-scoped roles.

For the complete list of templates, JSON format details, and instructions on applying templates via the CLI or API, see Developer Guide: Permissions.

Web route reference

The following web routes can be checked via the web object type with the route__path field. Requests for these routes are made through the POST /auth/routes API. These control front-end navigation visibility only and do not impact API data access.

Route PathPage
*Default landing page (always allowed)
/Default landing page (always allowed)
/assetIngestionAsset ingestion
/assetsAssets listing
/assets/:assetIdAsset detail
/auth/api-keysAPI key management
/auth/cognitousersAmazon Cognito user management
/auth/constraintsConstraint management
/auth/rolesRole management
/auth/subscriptionsSubscription management
/auth/tagsTag management
/auth/userrolesUser-role assignment
/databasesDatabase listing
/databases/:databaseId/assetsDatabase assets listing
/databases/:databaseId/assets/:assetIdAsset detail (database-scoped)
/databases/:databaseId/assets/:assetId/downloadAsset download
/databases/:databaseId/assets/:assetId/fileFile viewer
/databases/:databaseId/assets/:assetId/file/*File viewer (nested path)
/databases/:databaseId/assets/:assetId/uploadsModify asset uploads
/databases/:databaseId/pipelinesDatabase pipelines
/databases/:databaseId/workflowsDatabase workflows
/databases/:databaseId/workflows/:workflowIdWorkflow detail
/databases/:databaseId/workflows/createCreate workflow
/metadataschemaMetadata schema listing
/metadataschema/:databaseIdDatabase metadata schemas
/pipelinesPipeline listing
/pipelines/:pipelineNamePipeline detail
/searchSearch page
/search/:databaseId/assetsDatabase-scoped search
/uploadUpload page
/upload/:databaseIdDatabase-scoped upload
/workflowsWorkflow listing
/workflows/createCreate workflow

API route reference

The following API routes are registered in the API Gateway. Each route uses the api object type with the route__path field for Tier 1 authorization. The table also shows which data object types are checked at Tier 2 for each route.

note

Routes marked "No auth checks" bypass Tier 1 and Tier 2 authorization. Routes marked "API-level only" check Tier 1 but do not perform Tier 2 data entity checks.

Configuration and authentication routes

RouteMethodsTier 2 Object Type
/api/amplify-configGETNo auth checks
/api/versionGETNo auth checks
/secure-configGETNo Tier 2 checks (requires authentication header)
/auth/routesPOSTNo Tier 1 checks (POST is non-mutating, retrieves allowed routes)
/auth/loginProfile/\{userId\}GET, POSTAPI-level only

Database routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/databaseGETdatabasedatabaseId
/databasePOSTdatabasedatabaseId
/database/\{databaseId\}GET, PUT, DELETEdatabasedatabaseId
/bucketsGET----
/database/\{databaseId\}/metadataGET, POST, PUT, DELETEdatabasedatabaseId

Asset routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/assetsGETassetassetId, assetName, databaseId, assetType, tags
/assetsPOSTassetassetName, databaseId, tags
/database/\{databaseId\}/assetsGETassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}GET, PUTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/archiveAssetDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/deleteAssetDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/unarchiveAssetPUTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/metadataGET, POST, PUT, DELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/metadata/fileGET, POST, PUT, DELETEassetassetId, assetName, databaseId, assetType, tags

Asset file routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/database/\{databaseId\}/assets/\{assetId\}/listFilesGETassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/fileInfoGETassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/moveFilePOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/copyFilePOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/archiveFileDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/unarchiveFilePOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/deleteFileDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/deleteAssetPreviewDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/deleteAuxiliaryPreviewAssetFilesDELETEassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/createFolderPOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/setPrimaryFilePUTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/revertFileVersion/\{versionId\}POSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/download/stream/\{proxy+\}GET, HEADassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/auxiliaryPreviewAssets/stream/\{proxy+\}GET, HEADassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/downloadPOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/exportPOSTassetassetId, assetName, databaseId, assetType, tags

Asset version routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/database/\{databaseId\}/assets/\{assetId\}/createVersionPOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/revertAssetVersion/\{assetVersionId\}POSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/getVersionsGETassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/getVersion/\{assetVersionId\}GETassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/assetversions/\{assetVersionId\}PUTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/assetversions/\{assetVersionId\}/archivePOSTassetassetId, assetName, databaseId, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/assetversions/\{assetVersionId\}/unarchivePOSTassetassetId, assetName, databaseId, assetType, tags

Upload and ingestion routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/uploadsPOSTassetassetId, assetName, assetType, databaseId, tags
/uploads/\{uploadId\}/completePOSTassetassetId, assetName, assetType, databaseId, tags
/ingest-assetPOSTassetassetId, assetName, databaseId
RouteMethodsTier 2 Object TypeTier 2 Fields
/asset-linksPOSTasset (both from and to assets)assetId, databaseId, assetName, assetType, tags
/asset-links/single/\{assetLinkId\}GETasset (both from and to assets)assetId, databaseId, assetName, assetType, tags
/asset-links/\{assetLinkId\}PUTasset (both from and to assets)assetId, databaseId, assetName, assetType, tags
/asset-links/\{relationId\}DELETEasset (both from and to assets)assetId, databaseId, assetName, assetType, tags
/asset-links/\{assetLinkId\}/metadataGET, POST, PUT, DELETEasset (both from and to assets)assetId, databaseId, assetName, assetType, tags
/database/\{databaseId\}/assets/\{assetId\}/asset-linksGETassetassetId, assetName, databaseId, assetType, tags

Comment routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/comments/assets/\{assetId\}GETassetassetId, assetName, databaseId, assetType, tags
/comments/assets/\{assetId\}/assetVersionId/\{assetVersionId\}GETassetassetId, assetName, databaseId, assetType, tags
/comments/assets/\{assetId\}/assetVersionId:commentId/\{assetVersionId:commentId\}GET, POST, PUT, DELETEassetassetId, assetName, databaseId, assetType, tags

Pipeline and workflow routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/pipelinesGETpipelinedatabaseId, pipelineId, pipelineType, pipelineExecutionType
/pipelinesPUTpipelinedatabaseId, pipelineId, pipelineType, pipelineExecutionType
/database/\{databaseId\}/pipelinesGETpipelinedatabaseId, pipelineId, pipelineType, pipelineExecutionType
/database/\{databaseId\}/pipelines/\{pipelineId\}GET, DELETEpipelinedatabaseId, pipelineId, pipelineType, pipelineExecutionType
/workflowsGETworkflowdatabaseId, workflowId
/workflowsPUTworkflowdatabaseId, workflowId
/database/\{databaseId\}/workflowsGETworkflowdatabaseId, workflowId
/database/\{databaseId\}/workflows/\{workflowId\}GET, DELETEworkflowdatabaseId, workflowId
/database/\{databaseId\}/assets/\{assetId\}/workflows/\{workflowId\}POSTasset, workflow, pipeline(checks all three entity types)
/database/\{databaseId\}/assets/\{assetId\}/workflows/executionsGETasset, workflow(checks both entity types)
/database/\{databaseId\}/assets/\{assetId\}/workflows/executions/\{workflowId\}GETasset, workflow(checks both entity types)

Metadata schema routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/metadataschemaGET, POST, PUTmetadataSchemadatabaseId, metadataSchemaEntityType, metadataSchemaName
/database/\{databaseId\}/metadataSchema/\{metadataSchemaId\}GET, DELETEmetadataSchemadatabaseId, metadataSchemaEntityType, metadataSchemaName

Search route

RouteMethodsTier 2 Object TypeTier 2 Fields
/searchGET, POSTassetassetId, assetName, databaseId, assetType, tags (both GET and POST are non-mutating)

Subscription routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/subscriptionsGET, PUT, POST, DELETEassetassetId, assetName, databaseId, assetType, tags
/check-subscriptionPOSTassetassetId, assetName, databaseId, assetType, tags (non-mutating)
/unsubscribeDELETEassetassetId, assetName, databaseId, assetType, tags

Tag and tag type routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/tagsGET, PUT, POSTtagtagName
/tags/\{tagId\}DELETEtagtagName
/tag-typesGET, PUT, POSTtagTypetagTypeName
/tag-types/\{tagTypeId\}DELETEtagTypetagTypeName

Role and user role routes

RouteMethodsTier 2 Object TypeTier 2 Fields
/rolesGET, PUT, POSTroleroleName
/roles/\{roleId\}DELETEroleroleName
/user-rolesGET, PUT, POST, DELETEuserRoleroleName, userId

Auth and administration routes

RouteMethodsTier 2 Object Type
/auth/constraintsGETAPI-level only
/auth/constraints/\{constraintId\}GET, PUT, POST, DELETEAPI-level only
/auth/constraintsTemplateImportPOSTAPI-level only
/auth/api-keysGET, POSTAPI-level only
/auth/api-keys/\{apiKeyId\}GET, PUT, DELETEAPI-level only
/user/cognitoGET, POSTAPI-level only
/user/cognito/\{userId\}PUT, DELETEAPI-level only
/user/cognito/\{userId\}/resetPasswordPOSTAPI-level only

Performance considerations

The Casbin enforcer uses a 60-second in-memory policy cache per user per AWS Lambda execution environment. Policy changes (new constraints, role assignments) take effect within 60 seconds as the cache refreshes. For immediate effect, the user can re-authenticate to force a new Lambda cold start.