Aserto on Aserto: an OPA authorization policy for Aserto tenants

Permissions

{
"perms": {
"aserto.common.info.v1.Config.Get": {
"description": "aserto.common.info.v1.Config.Get"
},
"aserto.common.info.v1.Info.Info": {
"description": "aserto.common.info.v1.Info.Info"
},
"aserto.tenant.account.v1.Account.GetAccount": {
"description": "aserto.tenant.account.v1.Account.GetAccount"
},
"aserto.tenant.account.v1.Account.ListInvites": {
"description": "aserto.tenant.account.v1.Account.ListInvites"
},
"aserto.tenant.account.v1.Account.UpdateAccount": {
"description": "aserto.tenant.account.v1.Account.UpdateAccount"
},
"aserto.tenant.connection.v1.Connection.CreateConnection": {
"description": "aserto.tenant.connection.v1.Connection.CreateConnection"
},
...
}
}

Roles

  • tenant_viewer: read-only access to tenant data
  • tenant_member: read-write access to tenant data
  • tenant_admin: all of tenant_member, plus the ability to administer other users
  • tenant_owner: all of tenant_admin, plus the ability to invite users, and the restriction that there must always be at least one owner for the tenant
  • sys-admin: access to all APIs — whether they are tenant-scoped or system APIs
"tenant_viewer": {
"description": "tenant viewer",
"perms": {
"aserto.common.info.v1.Config.Get": {
"allowed": false
},
"aserto.common.info.v1.Info.Info": {
"allowed": true
},
"aserto.tenant.account.v1.Account.GetAccount": {
"allowed": true
},
"aserto.tenant.account.v1.Account.ListInvites": {
"allowed": true
},
"aserto.tenant.account.v1.Account.UpdateAccount": {
"allowed": false
},
"aserto.tenant.connection.v1.Connection.CreateConnection": {
"allowed": false
},
...
}
}

Policies

Information APIs

package aserto.common.info.v1.Info.Infodefault allowed = true

Global roles

some i
user.attributes.roles[i]
package aserto.tenant.system.v1.System.DeleteTenantimport input.user
import input.policy.path
default allowed = false# global role
allowed {
not user.enabled != true
some i
data.roles.roles[user.attributes.roles[i]].perms[path].allowed
}
  • First, we import the input.user and input.policy.path fields of the input payload, to make them available as user and path, respectively. user contains all the user attributes, including the roles the user is a member of, available as user.attributes.roles[]. path is set to the policy we are evaluating (in this case, aserto.tenant.system.v1.System.DeleteTenant).
  • Then we makes sure that the user.enabled flag isn’t set to false (if it is, the allowed decision evaluates to false)
  • data.roles.roles[user.attributes.roles[i]] evaluates to the role object for each role the user belongs to, and .perms[path] will index into the perms associated with that role, and fish out the object corresponding to our permission (aserto.tenant.system.v1.System.DeleteTenant).
  • Finally, we return the value of the allowed attribute and make that the result of the allowed decision.

Tenant-specific roles

  • input.resource[“Aserto-Tenant-Id”]: the ID of the Aserto tenant we’re trying to make a decision for, which is given to us as part of the resource context, which we bind to the t variable
  • user.applications[t]: the value of the user’s “application block” for that tenant ID. The application block is a map of data (like roles and permissions) stored for a user, usually keyed on some form of identifier; in Aserto’s case, we key on the Aserto tenant ID.
package aserto.tenant.connection.v1.Connection.CreateConnectionimport input.user
import input.policy.path
default allowed = false# global role
allowed {
not user.enabled != true
some i
data.roles.roles[user.attributes.roles[i]].perms[path].allowed
}
# tenant context role
allowed {
not user.enabled != true
t = input.resource["Aserto-Tenant-Id"]
a = user.applications[t]
some i
data.roles.roles[a.roles[i]].perms[path].allowed
}

Policies based on more than one identity

package aserto.authorizer.directory.v1.Directory.GetUserimport input.user
import input.policy.path
default allowed = false# global role
allowed {
not user.enabled != true
some i
data.roles.roles[user.attributes.roles[i]].perms[path].allowed
}
# allow reading your own user
allowed {
not user.enabled != true
targetID = input.resource["id"] user.id == targetID
}
# allow reading co-members of tenants
allowed {
not user.enabled != true
targetID = input.resource["id"]
targetUser = dir.user(targetID)
some i, j
user.applications[i]
targetUser.applications[j]
i == j
}

Driving UI behavior using the visible and enabled decisions

package aserto.tenant.profile.v1.Profile.InviteUserimport input.user
import input.policy.path
default allowed = false
default visible = true
default enabled = false
# global role
allowed {
not user.enabled != true
some i
data.roles.roles[user.attributes.roles[i]].perms[path].allowed
}
# tenant context role
allowed {
not user.enabled != true
t = input.resource["Aserto-Tenant-Id"]
a = user.applications[t]
some i
data.roles.roles[a.roles[i]].perms[path].allowed
}
enabled {
t = input.resource["Aserto-Tenant-Id"]
a = user.applications[t]
some i
a.roles[i] == "tenant_owner"
}

In Summary

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store