Designing an Authorization System: a Dialogue in Five Scenes

This posting’s setting is a blatant ripoff of perhaps the best technology overview document ever written (on Kerberos):

Designing an Authentication System: a Dialogue in Four Scenes

Abstract

This dialogue provides a fictitious account of the design of an open-source authorization system called “Haros”. As the dialogue progresses, the characters Athena and Euripides discover the problems inherent in applications using common authorization systems.

When they finish designing the system, Athena changes the system’s name to “Apache Fortress“, the name, coincidentally enough, of the authorization system that was designed and implemented at Apache’s “Directory Project“.

Contents

  • Dramatis Personae
  • Scene I
  • Scene II
  • Scene III
  • Scene IV
  • Scene V

Dramatis Personae

Athena a newly promoted director of IT security and a people person.
Euripides a longtime programmer specializing in security and reigning curmudgeon.

Scene I

A cubicle area. Euripides is working on a project rollout, for a new financial system.  Athena’s walking the floor with several other executives.  She finds Euripides in the farthest corner, away from the elevator.  All of the windows are covered and the lighting is muted.

Euripides: [clearing an old pizza box from the stained guest chair] Hey stranger!  Haven’t seen you in a while.  How’s life as a corporate bigwig?

Athena’s in distress but trying to conceal it.  Before the promotion, she and Euripides worked on several projects together, the most successful, a Kerberos authentication system.  Despite his gruff exterior, she’s learned to trust his candid viewpoint, particularly on security.

Athena:    [gingerly sitting down] Not so great Rip.  Our common security authorization system totally sucks.  It’s tightly bound to the application data model and can’t be reused. It’s impossible to separate the security policy data from the business data.  We’re going to have to build yet another authorization system for this new banking project you’re working on.

Euripides: [munching on a donut] Why are you telling me?  I’m just a programmer.

Athena:    [rolling her eyes] If only there was a way to externalize security policy so that it doesn’t taint the business model. That way we could have a common fine-grained authorization system that works across every application, on every system.

Euripides: [licking his fingers] We’ve been asking for one of those for as long as I’ve been here.  It’ll happen when Hades freezes over.  Here, have a donut, you’ll feel better.

Athena:     [cringing] No thanks.  I think it’s time to ask the Gods.  Maybe, this has already been solved and we can save my team the odyssey of creating a new one.

Euripides: [eating another donut] Good luck [muttering under his breath] you’re going to need it.

Athena:     What’s that?

Euripides: Oh uh yeah, tell ’em we need better authorization tools down here.

Athena:     [walking briskly away] You better believe it.

Scene II

Euripides’ cubicle, the next morning where he periodically spaces out while reading email.  Every so often, he’ll respond with a cryptic and/or sarcastic one-liner. A strong proponent of a style of discourse known as ‘cartoon-speak’, points are added for comedic effect.  Athena knocks on the cube wall startlingly him into the present.

Athena:     [excitedly] I found the answer to our entrenched authorization problem!

Euripides: [yawning and scratching] Isn’t that wonderful.  [feigning interest] That sure didn’t take very long.

Euripides moves a brown paper bag that’s oozing some kind of liquid from the guest chair to the floor and motions for her to have a seat.  She pretends not to notice the bag has something that looks like feathers sticking out of its top and that there’s a funny smell in the air.  Is there something still moving around in it?  She can’t be sure.

Athena:    [with a crinkled nose, electing to stand] As it turns out this problem has been studied for a very long time, like for ages.  Almost as long as you’ve been a programmer, not quite.

Euripides: [perking up slightly] Is that so?

Athena:    Right, there’s a research deity called NIST and they have many priests who sit in an ivory tower handing down directives for those of us living in the ‘real world’.

Euripides: I hate it when that happens.

Athena:    I know right?  So, the model I found is called Role-Based Access Control, and it’s governed by a specification from another powerful deity, ANSI, called INCITS 359.

Euripides: [sarcastically] That’s a real catchy name.

Athena:    We’ll just call it RBAC for short.

Euripides: RBAC, isn’t that what our Linux systems have… with those Group thingies?  We have to add ourselves to them, for root and what not.

Athena:    Not exactly.  There’s a bit more to it than that.

Euripides: Like what?

Athena:     There are Users of course.  And Groups, but they don’t call them that, rather they’re called Roles.

Euripides: Like for dinner?

Athena:     Very funny.  Roles are how Users are assigned to Permissions.  Permissions are a combination of Resources and Operations.

Euripides: Sounds simple enough.  Is that it?

Athena:     Not quite.  There’s also an entity called a Session.  That’s where Roles go after the User logs in.  One or more of their assigned Roles are then activated into the Session.

Euripides: [smirking] Kind of like dinner rolls only they’re not done yet, so we have to put them in the oven?  Seems like a waste of time.  Why not just let the people eat ’em uncooked?

Athena:     [scowling but with patience] This is keeping with the principle of least privilege.  The User is given the minimum authority to complete her meal, err tasks.  Think about when someone goes on vacation or perhaps on a maternity leave.  Do they need to be active in the system then?

Euripides: No, I suppose not.  [hopefully] Is that it?

Athena:    There’s more to it.  What we’ve described so far is known as RBAC0.  The absolute minimum that an application must do in order to be compliant.  Many times that’s all that’s needed.

Euripides: [sighing] What else is there?

Athena:    Well I’m still digging, let’s talk later.

Euripides: [relieved] Sounds good.

Scene III

The next morning, Athena catches Euripides at the Starbucks kiosk inside their business plaza. He’s waiting on coffee, listening to Spotify and pretends not to see her.  She taps him on the shoulder, forcing him to acknowledge her.

The two then grab their coffees, head to the condiment bar pouring in lots of creamer to mask the bitterness, before tucking themselves into a nearby booth.

Euripides: [audibly slurping] Well that didn’t take very long.

Athena:    [wincing] Got it all figured out.  Do you want to hear the rest of the story?

Euripides: Do I have a choice?

Athena:    Very funny.  Don’t make me place a hex on your dog.  Where were we?  Oh yeah, going over the various facets of the RBAC specification.

Euripides: I’m on the edge of my seat here.

Athena:    [clears throat] RBAC1 is for Hierarchical Roles.  That is we place inheritance relationships between Roles.

Euripides: Kind of like they’re in a family?

Athena:    Sort of.  Think about when it comes time to assign the Roles to Users, sometimes referred to as ‘Role engineering’.  It’s a pretty tricky task as you might imagine.  There are many levels of access.  A Role can be a kind of module, and linked to others via inheritance.  Perhaps Engineers need to also inherit all of the permissions that a normal User gets, plus everything that is normal for them.

Euripides: [caffeine kicking in] I see.  It saves the trouble of having to constantly update all of the Roles anytime the Permissions change for a particular use case.  Say we don’t want Users to be able to log into System X, we don’t have to change every Role, we only need to change the one.  The others will realize the change automatically via inheritance.  Also, the security administrator doesn’t have to understand that an Engineer is also a User.  They just assign them the one, and that includes the other.

Athena:    Very good.  You really are more than just a programmer.  But wait, there’s more.  RBAC2 is Static Separation of Duties.  This is where we establish mutual exclusion constraints between assigned Roles.

Euripides: Say what?

Athena:    It’s not all that complicated.  Basically, we can define sets of Roles and establish a cardinality between them.  That is out of this entire set, only so many can be assigned to a given User.

Euripides: Why would we do that?

Athena:     Think about conflicts of interests that arise in everyday scenarios.  For example, we don’t want the person writing checks also approving them.

Euripides: Why not?

Athena:     Well then they could write themselves checks, approve and then deposit into their bank accounts, illegally.

Euripides: That makes sense.

Athena:    There’s also RBAC3, Dynamic Separation of Duties.  It’s basically the same concept only with activated Roles not assigned.

Euripides: Oh, here we’re back to the Session right?  One can be assigned the ‘check writer’ and ‘check approver’ Roles but can never activate them together into a Session.  There’s a toxic relationship there.

Athena:    Very good.  You’re getting the hang of this Rip.

Euripides:  [brushing off the compliment] It’s pretty straightforward.  Ok, what else?

Athena:     That’s pretty much it.  Now, I go in front of the architecture review board and try to sell my idea of using RBAC authorization inside our business apps.  Hopefully we can stop building new authorization utilities every time we build a new app.

Euripides: Good luck.

Athena:    Why do you keep saying that?

Scene IV

After not hearing from Athena for a week, and no sign of her on slack, Euripides gets worried, and tracks her GPS location on Google to a courtyard inside their business campus, overlooking a large pond.  He finds her slumped over a park bench by the shoreline, in obvious despair.  A flotilla of turtles nearby, observing the scene somberly.

Euripides: Whoa!  Look alive there sailor!

The turtles scatter.

Athena:    [Lifting her head up] I was eaten alive by the board.

Euripides: How so?

Athena:    They hated my idea.  Told me they already understood RBAC, and it’s stupid.

Euripides: Why do they say that?

Athena:     They called it quaint and said it hasn’t worked in the ‘real world’ for a long time.

Euripides: Did you tell them the NIST Gods control the real world?

Athena:     [tearfully] Yes, but they didn’t believe me.  They said that fine-grained authorization is nearly impossible to externalize because it’s tightly bound to the application’s business rules.  For an RBAC system to compensate, too many Roles would be needed causing explosions, making a huge mess of our datacenter.

Euripides: What in hades are you talking about?

Athena:     I’m talking about ‘Role explosion’ and it happens any time context is introduced into an RBAC policy.

Euripides: Context… like attributes and such?

Athena:     Yes, like in our banking scenario we have an attribute associated with the location of the financial institution, or branch.  There are over 1,000 separate locations today and our business plan calls for 10X that over the next few years.  How do we specify that someone can be a Teller in one location and not in another?

Euripides: That’s easy.  Just create Roles with the location as part of the name.  Say Teller-North123 and Washer-South456.

Athena:    That’s what I told them too.  But they said the number of Roles gets multiplied by the number of locations.  So if we have two Roles: Teller and Money Washer, and 1,000 locations, we end up with over 2,000 Roles to manage!

Euripides: Ay yi yi I see the problem.

Athena:    The architects say the only way to fix it is to join the policy entities, i.e. User, Role, and Perm, with the Bank’s data, that includes its locations.  This is hopeless because every application must have its own policy engine, one that understands its particular data model.

Euripides: Sounds like we’re back to square one.  Now what?

Athena:     Well, they’re talking about this new kind of authorization system called Attribute-Based Access Control, or ABAC.  It downplays the Role.  Everything is just an attribute, that can then get linked with a User at runtime.

Euripides: I mean, isn’t that just an Access Control List?

Athena:     Well yeah, but there’s more, like dynamic policies.  In ABAC, policies are expressions in which the various linked attributes are combined with the traditional security entities like Roles and Resources.   Let’s say that I have a policy where the ‘Teller’ Role is still required to gain access to a cash drawer at a financial institution.  We can also link additional attributes, like location.  These attributes can come from anywhere.  Something from the runtime environment, like location, time, IP address, platform, temperature of processors.  Attributes also come from the the application domain.  Things like hair color, favorite type of beer, how many donuts eaten yearly, literally anything under the helios.

Euripides: [rubbing his belly] That sounds loosey-goosey to me and again we still have our security data tightly bound to the application data.

Athena:    You haven’t even heard the worst of it yet.  The most popular ABAC systems use XML to transmit the data between their intermediaries.

Euripides: [retracting in horror] That’s bloody awful.

Athena:    [despondently] If only there was a way to combine RBAC and ABAC.  We could keep our common security data model and policy engine and combine a few attributes, like location, as needed.

Euripides: Holy Toledo, is that even allowed by the NIST Gods?

Athena:    I don’t know but I’m going to find out.

Euripides: Good luck.

Athena:     Stop saying that.

Scene V

The following week at Euripides cubicle.  Athena knocks, startling Euripides from a mid-afternoon siesta.

Euripides: [groggy and regretful of a third IPA during lunch] There you are.  How’s it going today… have you found a solution to your exploding Roles?

Athena:    [grinning] I sure did, and the architecture board has approved my plan for externalizing authorization.

Euripides: Wuff.  How’d you manage to pull that off?

Athena:     It was surprisingly easy.  First, there’s nothing in RBAC that says that attributes aren’t allowed.  So from a standards perspective we’re in the clear and the NIST Gods can’t hex our offspring.

Euripides: [nervously looking at a framed portrait of a dog on the cubicle wall] Oh, yeah, that’s good.  Did the NIST high priests tell you how?

Athena:    They gave me some ideas.  There’s the user-role activation phase… back to our Banking example… remember the two Roles and 1,000 locations?

Euripides: [groaning] Like it was yesterday.

Athena:    As we learned earlier, in RBAC, assigned Roles must be activated into the Session before a particular User can perform operations on the Role’s corresponding resources.

Euripides realizing he’s famished, snatches a cookie wedged against a burger from a day-old takeout box on his desk, almost takes a bite, thinks better of it, (trying to be nice) offers it to Athena, who vigorously shakes her head and frowns.  He then shrugs and begins eating it, a bit of ketchup hanging from one side dropping onto his shirt.

Euripides: [talking with his mouth full] How could I forget?

Athena:     [disgusted] Anyway, we can add a constraint during activation to detect whether the Role being activated matches location.  For example, only activate the Teller Role for Curly when he’s at the North branch.

Euripides: [takes a swig from a two-liter of mountain dew and burps with evident gusto] ‘scuse me.  That’s the policy but how is it enforced?

Athena:     Could … you …

Euripides: Wha?

Athena:     [waving her hands] ew! Just … never mind.  We’re making the constrained Roles special to the system.  The policy engine has to know when activating any Role, that it might have a dynamic constraint placed upon it.  Here, a property associated with the Role’s name will do quite well.

[clears a tiny space on the cluttered whiteboard and begins drawing]:

Role: Teller:locale

Role: Washer:locale

Euripides: This covers how the authorization system knows when to check for a dynamic constraint, but where will the actual attribute values be stored?

Athena:    On the User’s entity of course.  With RBAC, there is already a User-Role assignment that is bound to the User entity.  We simply need to place a couple of delineated properties on that assignment. For example the Role assignment looks like this:

[draws on whiteboard]:

User: Curly

Role Assignment: Teller:locale:North123

Role Assignment: Washer:locale:South456

Euripides: We’re still missing a piece here.  How does the authorization system get the current valid values of a dynamic constraint?  For our scenario, which locale they’re in?

Athena:    It just pushes that into the runtime context…

[draws on whiteboard]:

Session session = createSession("Curly", "locale:North123");

Euripides: Ah!  Now, when the runtime activates Curly’s Roles, it knows that they’re special and will compare the value of the constraint pushed into the API with that stored on the User-Role assignment.  Here, Curly will be a Teller because we’re in the North, right?

Athena:     That’s right. There really is more to you than your curmudgeonly demeanor suggests.

Euripides: [reflectively, with a bit of ketchup still on his shirt] Yep.  Sometime’s it’s hard for me to be humble.  What’d the board have to say?

Athena:    [handing him a napkin] What could they?  We’re pushing into the financial system as we speak.  The Bank is happy because now we’re not spending a bunch of money on a new ABAC system.  We just made a couple tweaks to our old RBAC system and everyone’s happy.

Euripides: The Gods must be pleased, maybe we’ll finally get raises.

Athena:    Don’t push your luck.

Who put ABAC in my RBAC?

Readers know that Attribute-based Access Control (ABAC) is a bit of an obsession with me.  It stems from the want to have something like an ABAC system in my little bag of tricks.  An authorization engine that scales to everyday usage, without proprietary, bloated or cumbersome baggage to weigh it down.

So I comment, lament and nothing seems to come of it.

Until I leaned that ABAC can be combined with RBAC.

We like RBAC, use it in our everyday applications, but it has some serious shortcomings, and we don’t know what to do about them.

ABAC also good.  It’s adaptable, but lacks meaningful standards, we struggle during implementations, and are left wanting more.

Now, let’s somehow combine the two.  Hopefully allowing the strengths of each to be preserved while eliminating their shortcomings.

What would such a system look like?

  1. Simple apis that are easy to understand and use.
  2. Standard data and api formats, something that can be shared between all of my apps and systems.
  3. Flexible decision expressions allowing unlimited instance data types and values to be considered.

How would this system work?

Standards-based RBAC adheres to the NIST model, later becoming an ANSI standard — INCITS 359.  Long story short, RBAC allows attributes to be applied during two separate phases of the access control decision:

1. User-Role Activation – instance data used to constrain whether an assigned role is eligible to be considered in the access control decision, i.e. permission check, that happens later.  For example, user may only activate the cashier role at store 314.

2. Role-Permission Activation – these constraints apply during the permission check itself.  An example is the action may only be performed if account #456789.

Apache Fortress 2.0.2 now supports type 1.  For a test drive, there’s this rbac-abac-sample in Github.  Have a look under-the-hood section of the README.

 

Towards an Attribute-Based Role-Based Access Control System

[Link to the Apache Fortress RBAC-ABAC-SAMPLE project on Github]

[Link to towards-an-attribute-based-rbac-ldapcon-2019-v1 from LDAPCon]

We’ve all heard the complaint, RBAC doesn’t work.  It leads to Role Explosion, defined as an inordinate number of roles in a production environment.  Nobody knows who must be assigned to what because there are hundreds if not thousands of them.

What’s a system implementor to do?  We could give Attribute-Based Access Control a try, but that has its own problems and we need not go there again.

There’s another way.  RBAC allows the usage of dynamic attributes.

  • Recent standards include dynamic policies, most notably, ANSI INCITS 494 RBAC Policy-Enhanced
  • The existence of entities to conveniently apply dynamic policies, e.g. User-Role and Role-Permission.
  • No language discouraging the usage of dynamic attributes alongside RBAC in the standard.

Indeed, dynamic attributes are encouraged if not prescribed.  Here’s where I should be pointing to evidence substantiating my arguments.

http://lmgtfy.com/?q=NIST+and+ANSI+and+RBAC+and+attributes

This brings us to Apache Fortress and a new enhancement to use dynamic attributes.

What is Apache Fortress?

Both followers of this blog (wife and boss) know about Apache Fortress.  Especially my wife.  It’s the itch that leads me to three years of work in a garage, alongside two of my brothers, who got dragged in also.

It’s also an implementation of the classic RBAC specification – ANSI INCITS 359.  If anything’s prone to exploding roles, it’s Apache Fortress.

How are we going to stop the dang exploding?

Described in a JIRA ticket  yesterday, and checked into the Apache Fortress Core Repo last night.  The idea is best explained with a story.

The Tale of Three Stooges and Three Branches

Once upon a time there were three branches, North, South and East managed by The Three Stooges that worked there, Curly, Moe and Larry.

They were nice blokes, but a tad unruly, and so we try to keep them separated.  Curly works in the East, Moe the North and Larry runs amok in the South.  All three are Tellers, but each may also substitute as coin Washers at the other two.

All is well because each Branch has only one Teller.  It’s never good when two Stooges combine without one being in charge.

Here are the Users and their Role assignments:

Curly: Teller, Washer

Moe: Teller, Washer

Larry: Teller, Washer

By now we know where this storyline’s headed.  How do we prevent one going off-script, wandering into another branch, activating Teller, and running slipshod?

The classic Role explosion theory goes like…

Create Roles by Location with User-Role assignments:

Curly: TellerEast, WasherNorth, WasherSouth

Moe: TellerNorth, WasherEast, WasherSouth

Larry: TellerSouth, WasherNorth, WasherEast

This works pretty good with three branches and two roles but what about the real-world?  How many branches will the medium-sized bank have, a thousand?  How many types of roles, at least ten?  If we follow the same Role-by-Location pattern there’d be over 10,000 Roles to manage!  We may be keeping our Stooges in check, but at the IT team’s expense.  Our roles have indeed exploded.  What now?

Time for something different, back to the earlier discussion over using attributes.  Let’s try controlling role activation by location, but store the required attributes on the user object itself.

User-Properties to store Role-Locale constraints:

Curly: Teller:East, Washer:North, Washer:South

Moe: Teller:North, Washer:East, Washer:South

Larry: Teller:South, Washer:North, Washer:East

What just happened here?  It kind of looks the same but it’s not.  We go back to only needing two Roles, but have added dynamic policies, Role-Locale, to properties stored on the User.  Our medium-sized bank only needs 10 roles not 10,000.

Now, when the security system logs in a User (createSession), it pushes its physical location attribute into the runtime context, e.g. North, South or East, along with the already present Userid attribute.  The security system compares that physical location, along with its corresponding properties stored on the User, to determine access rights, specifically which Roles may be activated into their Session.

Sprinkle in a policy that defines the role to constraint relationships.

Global Config Properties store Role-Constraint mappings:

Teller:Locale

Washer:Locale

That way when the security system activates roles it knows to perform the extra check on a particular role, and which attribute to verify.

In addition to location, we can constrain role activation by project, organization, customer, account balance, hair color, favorite ice cream, and any other form of instance data imaginable.  There may be multiple types of constraints applied to any or all roles in the system.  It truly is a dynamic policy mechanism placed on top of a traditional Role-Based Access Control System.

With this minor change to the security system, our IT guys return to the good life without worrying about exploding roles or what the Stooges are up to.  🙂

The End

 

 

New Sheriff in Town

And it don’t need no stinking badges.  Yeah, I’m mixing clichés, happens sometimes when coding long hours in a stretch.  🙂

What I’m talking about is a new access management system, released to PyPI yesterday for the first time.

py-fortress on PyPI

Considering we just started coding a couple of months ago that’s pretty good progress.

What is it?  A toolkit designed for Python3, with APIs that developers can use to do security in an RBAC-compliant way.  Today, it requires an LDAP server to store the policies, but a file-based backend will soon be ready.  No, I’m not recommending files in production, but it’s fine for getting started, within dev envs.

You can check the py-fortress project here:

py-fortress README on GitHub

Inside are links to some documents to help get started.  The quickstart doesn’t require cloning the project GIT repo, but you’ll need a Linux machine, Python3, PIP and Docker engine installed.  Everything else is covered.

Why would you want it?  That’s a long story.  It starts with an imperative to follow standards, in security processes, like authentication and authorization.  That there’s value in committing to best-practices, in this case ANSI RBAC.

It may help to know that this effort is backed by my employer — Symas.

Eventually, this code may end up inside of an Apache project, like Apache Fortress.  Or, it might land somewhere else.  It’s too early to know.  What’s certain is that it’ll remain open, available to use and learn from.

Programming in Python using the py-fortress RBAC APIs

py-fortress implements standards-based RBAC in Python. There have been numerous postings lately here about it.

Prerequisites

Very soon we’ll have a release of py-fortress that works with just the file system. This is to lower complexity of testing and developing with this package. In the meantime, an LDAP server has to be running on the network, so follow the instructions in the quickstart below to use a Docker container, if you don’t already have one installed.

0. Prepare your python module for usage by importing:
from pyfortress import (
    # model
    User,
    Role,
    Perm,
    PermObj,
    # apis:
    review_mgr, 
    admin_mgr, 
    access_mgr,
    #exception handling:
    FortressError,
    global_ids
)

Access Mgr APIs – Create Session, Check Access, Session Perms

These are used to check the policies at runtime. For example, to authenticate is create_session and authorization is check_access here.

  1. Now test signing on the RBAC way:
    def test_create_session(self):
        """
        create session
        """
        print('test_create_session')
        
        try:
            session = access_mgr.create_session(User(uid='foo1', password='secret'), False)
            if not session:
                print('test_create_session fail')
                self.fail('test_create_session fail')
            else:
                print('test_create_session pass')
                pass                        
        except FortressError as e:
            self.fail('test_create_session failed, exception=' + e.msg)

    The session will then be held on to by the client for subsequent calls like check_access and session_perms

  2. Here’s how to check a single permission:
    def test_check_access(self):
        """
        create session and check perm
        """
        print('test_check_access')
        
        try:
            session = ... obtained earlier
            result = access_mgr.check_access(session, Perm(obj_name='ShoppingCart', op_name='add'))
            if not result:
                print('test_check_access fail')
                self.fail('test_check_access fail')
            else:
                print('test_check_access pass')
                pass                        
        except FortressError as e:
            self.fail('test_check_access failed, exception=' + e.msg)
  3. Retrieve all of the permissions as a list:
    def test_session_perms(self):
        """
        create session and get perms for user
        """
        print('test_check_access')
        
        try:
            session = ... obtained earlier
            perms = access_mgr.session_perms(session)
            if not perms:
                print('test_session_perms failed')
                self.fail('test_session_perms failed')
            
            for perm in perms:
                print_perm(perm, 'session_perms: ')
            pass                        
        except FortressError as e:
            self.fail('test_session_perms failed, exception=' + e.msg)

Admin and Review APIs – Create, Read, Update, Delete

These are for programs that manage and search the data. For example admin guis, conversion programs, reporting apps.

  1. Add a user:
    def test_add_user(self):
        """
        Add a basic user
        """
        print('test_add_user')
        
        try:
            admin_mgr.add_user(User(uid='foo1', password='secret'))
            print('test_add_user success')                        
        except FortressError as e:
            self.fail('test_add_user failed, exception=' + e.msg)
  2. Add a role:
    def test_add_role(self):
        """
        Add a basic role
        """
        print('test_add_role')        
        try:
            admin_mgr.add_role(Role(name='Customer'))
            print('test_add_role success')                        
        except FortressError as e:
            self.fail('test_add_role failed, exception=' + e.msg)
  3. Add an object:
    def test_add_obj(self):
        """
        Add a basic perm object
        """
        print('test_add_obj')
        
        try:
            admin_mgr.add_object(PermObj(obj_name='ShoppingCart'))
            print('test_add_obj success')                        
        except FortressError as e:
            self.fail('test_add_obj failed, exception=' + e.msg)
  4. Add a perm:
    def test_add_perm(self):
        """
        Add a basic perm
        """
        print('test_add_perm')
        
        try:
            admin_mgr.add_perm(Perm(obj_name='ShoppingCart', op_name='add'))
            print('test_add_perm success')                        
        except FortressError as e:
            self.fail('test_add_perm failed, exception=' + e.msg)
  5. Assign a user:
    def test_assign_user(self):
        """
        Assign a user to a role
        """
        print('test_assign_user')
        
        try:
            admin_mgr.assign(User(uid='foo1'), Role(name='Customer'))
            print('test_assign_user success')                        
        except FortressError as e:
            self.fail('test_assign_user failed, exception=' + e.msg)
  6. Grant a permission:
    def test_grant_perm(self):
        """
        Grant a permission to a role
        """
        print('test_grant_perm')
        
        try:
            admin_mgr.grant(Perm(obj_name='ShoppingCart', op_name='add'), Role(name="Customer"))
            print('test_grant_perm success')                        
        except FortressError as e:
            self.fail('test_grant_perm failed, exception=' + e.msg)

 

7. Read a user:

def test_read_user(self):
    """
    Read a user that was created earlier. Expects a unique uid that points to an existing user.
    """
    print_test_name()
    try:
        out_user = review_mgr.read_user(User(uid='foo1'))
        print_user(out_user)
    except FortressError as e:            
        print_exception(e)
        self.fail()

thows exception if user is not present

8. Search for users with a matching uid:

def test_search_users(self):
    """
    Search for users that match the characters passed into with wildcard appended.  Will return zero or more records, one for each user in result set.
    """
    print_test_name()
    try:
        users = review_mgr.find_users(User(uid='foo*'))
        for user in users:
            print_user(user)
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type user

9. Search for users assigned a role:

def test_assigned_users(self):
    """
    Search for users that are assigned a particular role.  Will return zero or more records, one for each user in result set.
    """
    print_test_name()
    try:
        uids = review_mgr.assigned_users(Role(name='Customer'))
        for uid in uids:
            print_test_msg('uid=' + uid)
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type string contain the user id

10. Search for users who have a permission:

def test_perm_users(self):
    """
    Search for users that have been authorized a particular permission.  Will return zero or more records, of type user, one for each user authorized that particular perm.
    """
    print_test_name()
    try:
        users = review_mgr.perm_users(Perm(obj_name='ShoppingCart', op_name='add'))
        for user in users:
            print_user(user)
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type user

11. Read a role:

def test_read_role(self):
    """
    The read role expects the role name to point to an existing entry and will throw an exception if not found or other error occurs.
    """
    print_test_name()        
    try:
        out_role = review_mgr.read_role(Role(name='Customer'))
        print_role(out_role)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a single entity of type role

12. Search for roles matching a particular name:

def test_search_roles(self):
    """
    Search for roles that match the characters passed into with wildcard appended.  Will return zero or more records, one for each user in result set.
    """
    print_test_name()        
    try:
        roles = review_mgr.find_roles(Role(name='Customer*'))
        for role in roles:
            print_role(role)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type role

13. Search for roles assigned to a user:

def test_assigned_roles(self):
    """
    Return the list of roles that have been assigned a particular user.  Will return zero or more records, of type constraint, one for each role assigned to user.
    """
    print_test_name()        
    try:
        constraints = review_mgr.assigned_roles(User(uid='foo1'))
        for constraint in constraints:
            print_test_msg('role name=' + constraint.name)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type role_constraint

14. Search for roles who have granted a particular permission:

def test_perm_roles(self):
    """
    Return the list of roles that have granted a particular perm.  Will return zero or more records, containing the role names, one for each role assigned to permission.
    """
    print_test_name()        
    try:
        names = review_mgr.perm_roles(Perm(obj_name='ShoppingCart', op_name='add'))
        for name in names:
            print_test_msg('role name=' + name)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of strings containing the role names

15. Read an object:

def test_read_obj(self):
    """
    The ob_name is the only required attribute on a fortress object. Will throw an exception if not found.
    """
    print_test_name()        
    try:
        out_obj = review_mgr.read_object(PermObj(obj_name='ShoppingCart'))
        print_obj(out_obj)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

return a single entity of type object

16. Search for objects matching obj_name field:

def test_search_objs(self):
    """
    Search for ojects that match the characters passed into with wildcard appended.  Will return zero or more records, one for each user in result set.
    """
    print_test_name()        
    try:
        objs = review_mgr.find_objects(PermObj(obj_name='ShoppingCart*'))
        for obj in objs:
            print_obj(obj)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type object

17. Read a permission entry:

def test_read_perm(self):
    """
    Permissions require obj_name and op_name, obj_id is optional.  This will throw an exception if not found.
    """
    print_test_name()        
    try:
        out_perm = review_mgr.read_perm(Perm(obj_name='ShoppingCart', op_name='add'))
        print_perm(out_perm)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a single entity of type permission

18, Search for perms:

def test_search_perms(self):
    """
    Search for perms that match the characters passed into with wildcard appended.  Will return zero or more records, one for each user in result set.
    """
    print_test_name()        
    try:
        perms = review_mgr.find_perms(Perm(obj_name='ShoppingCart*', op_name='*'))
        for perm in perms:
            print_perm(perm)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type perm

19. Search for perms by role:

def test_role_perms(self):
    """
    Search for perms that have been granted to a particular role.  Will return zero or more records, of type permission, one for each grant.
    """
    print_test_name()        
    try:
        perms = review_mgr.role_perms(Role(name='Customer'))
        for perm in perms:
            print_perm(perm)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type permission

20. Search for perms by user:

def test_user_perms(self):
    """
    Search for perms that have been authorized to a particular user based on their role assignments.  Will return zero or more records, of type permission, one for each perm authorized for user.
    """
    print_test_name()        
    try:
        perms = review_mgr.user_perms(User(uid='foo1'))
        for perm in perms:
            print_perm(perm)                        
    except FortressError as e:
        print_exception(e)
        self.fail()

returns a list of type permission

END

Testing the py-fortress RBAC0 System

The Command Line Interpreter (CLI) may be used to drive the RBAC System APIs,  to test, verify and understand a particular RBAC policy.

This document also resides here: README-CLI-AUTH

Prerequisites

Sample RBAC0 Policy

  • This tutorial covers the basics, RBAC Core: Many-to-many relationships between users, roles and perms and selective role activations.
  • py-fortress adds to the mix one non-standard feature: constraint validations on user and role entity activation.
  • The simple policy includes constraints being setup on user and role. Later we’ll demo a role timing out of the session.

Users

uid timeout begin_time end_time
chorowitz 30min

Roles

name timeout begin_time end_time
account-mgr 30min
auditor 5min

constraints are optional and include time, date, day and lock date validations

User-to-Role Assignments

user account-mgr auditor
chorowitz true true

Permissions

obj_name op_name
page456 edit
page456 remove
page456 read

Role-to-Permissions

role page456.edit page456.remove page456.read
account-mgr true true false
auditor false false true

 Getting Started

The syntax for testing py-fortress system commands:

clitest operation --arg1 --arg2 ... 

Where clitest executes a package script that maps to this module:

pyfortress.test.cli_test_auth

The operation is (pick one)

  •  auth => access_mgr.create_session
  • check => access_mgr.check_access
  • roles => access_mgr.session_roles
  • perms => access_mgr.session_perms
  • add => access_mgr.add_active_role
  • drop => access_mgr.drop_active_role
  • show => displays contents of session to stdout

Where operations => functions here: access_mgr.py

The args are ‘–‘ + attribute name + attribute value

  • –uid and –password from user.py
  • –obj_name, –op_name and –obj_id from perm.py
  • –role used for the role name

Command Usage Tips

  • The description of the commands, i.e. required and optional arguments, can be inferred via the api doc inline to the access_mgr module.
  • This program ‘pickles’ (serializes) the RBAC session to a file called sess.pickle, and places in the executable folder.  This simulates an RBAC runtime to test these commands.
  • Call the auth operation first, subsequent ops will use and refresh the session.
  • Constraints on user and roles are enforced. For example, if user has timeout constraint of 30 (minutes), and the delay between ops for existing session exceeds, it will be deactivated.

___________________________________________________________________________________

Setup an RBAC Policy Using admin_mgr CLI

To setup RBAC test data, we’ll be using another utility that was introduced here: README-CLI.md.

From the py-fortress/test folder, enter the following commands:

1. user add – chorowitz

$ cli user add --uid chorowitz --password 'secret' --timeout 30

user chorowitz has a 30 minute inactivity timeout

2. role add – account-mgr

$ cli role add --name 'account-mgr'

3. role add – auditor

$ cli role add --name 'auditor' --timeout 5

role auditor has a 5 minute inactivity timeout, more later about this…

4. user assign – chorowitz  to role account-mgr

$ cli user assign --uid 'chorowitz' --role 'account-mgr'

5. user assign – chorowitz to role auditor

$ cli user assign --uid 'chorowitz' --role 'auditor'

6. object add – page456

$ cli object add --obj_name page456

7. perm add – page456.read

$ cli perm add --obj_name page456 --op_name read

8. perm add – page456.edit

$ cli perm add --obj_name page456 --op_name edit

9. perm add – page456.remove

$ cli perm add --obj_name page456 --op_name remove

10. perm grant – page456.edit to role account-mgr

$ cli perm grant --obj_name page456 --op_name edit --role account-mgr

11. perm grant – page456.remove to role account-mgr

$ cli perm grant --obj_name page456 --op_name remove --role account-mgr

12. perm grant – page456.read  to role auditor

$ cli perm grant --obj_name page456 --op_name read --role auditor

________________________________________________________________________________

Perform cli_test_auth.py access_mgr Commands

From the py-fortress/test folder, enter the following commands:

1. auth – access_mgr.create_session – authenticate, activate roles:

 $ clitest auth --uid 'chorowitz' --password 'secret'
 uid=chorowitz
 auth
 success

Now the session has been pickled in on file system in current directory.

2. show – output user session contents to stdout:

$ clitest show
show
session
    is_authenticated: True
    user: 
    last_access: 
user
    cn: chorowitz
    constraint: 
    system: []
    roles: ['account-mgr', 'auditor']
    dn: uid=chorowitz,ou=People,dc=example,dc=com
    uid: chorowitz
    internal_id: 552c1a24-5087-4458-98f1-8c60167a8b7c
    reset: []
    sn: chorowitz

    User Constraint:
        name: chorowitz
        raw: chorowitz$30$$$$$$$
        timeout: 30
    User-Role Constraint[1]:
        name: account-mgr
        raw: account-mgr$0$$$$$$$
    User-Role Constraint[2]:
        name: auditor
        raw: auditor$5$$$$$$$
        timeout: 5
success

Displays the contents of session to stdout.

3. check – access_mgr.check_access – perm page456.read:

$ clitest check --obj_name page456 --op_name read
op_name=read
obj_name=page456
check
success

The user has auditor activated so unless timeout validation failed this will succeed.

4. check – access_mgr.check_access – perm page456.edit:

$ clitest check --obj_name page456 --op_name edit
op_name=edit
obj_name=page456
check
success

The user has account-mgr activated and this will succeed.

5. check – access_mgr.check_access – perm page456.remove:

$ clitest check --obj_name page456 --op_name remove
op_name=remove
obj_name=page456
check
success

The user has account-mgr activated and this will succeed.

6. get – access_mgr.session_perms:

$ clitest perms
perms
page456.read:0
  abstract_name: page456.read
  roles: ['auditor']
  internal_id: d6887434-050c-48d8-85b0-7c803c9fcf07
  obj_name: page456
  op_name: read
page456.edit:1
  abstract_name: page456.edit
  roles: ['account-mgr']
  internal_id: 02189535-4b39-4058-8daf-af0e09b0d235
  obj_name: page456
  op_name: edit
page456.remove:2
  abstract_name: page456.remove
  roles: ['account-mgr']
  internal_id: 10dea5d1-ff1d-4c3d-90c8-edeb4c7bb05b
  obj_name: page456
  op_name: remove
success

Display all perms allowed for activated roles to stdout confirms that user indeed can read, edit and remove from Page456.

7. drop – access_mgr.drop_active_role – auditor:

$ clitest drop --role auditor
drop
role=auditor
success

RBAC distinguishes between roles assigned or activated and privileges can be altered in the midst of a session.

8. roles – access_mgr.session_roles

$ clitest roles
roles
account-mgr:0
  raw: account-mgr$30$$$20180101$none$$$1234567
  end_date: none
  name: account-mgr
  timeout: 30
success

Notice the audit role is no longer active.

9. check – access_mgr.check_access – perm page456.read (again):

$ clitest check --obj_name page456 --op_name read
op_name=read
obj_name=page456
check
failed

The auditor role was deactivated so even though it’s assigned, user cannot perform as one.

10. add – access_mgr.add_active_role – auditor:

$ clitest add --role auditor
add
role=auditor
success

Now the user should be allowed to resume audit activities.

11. roles – access_mgr.session_roles:

$ clitest roles
roles
account-mgr:0
  raw: account-mgr$30$$$20180101$none$$$1234567
  end_date: none
  name: account-mgr
  timeout: 30
auditor:1
  raw: auditor$5$$$20180101$none$$$1234567
  timeout: 5
  name: auditor
  begin_lock_date: success
success

Notice the audit role has been activated once again.

12. check – access_mgr.check_access – perm page456.read (for the 3rd time):

$ clitest check --obj_name page456 --op_name read
op_name=read
obj_name=page456
check
success

The auditor role activated once again so user can do auditor things again.

13. Wait 5 minutes before performing the next step.

Allow enough time for auditor role timeout to occur before moving to the next step. Now, if you run the roles command, the auditor role will once again be missing.  This behavior is controlled by the ‘timeout’ attribute on either a user or role constraint.

14. check – access_mgr.check_access – perm page456.read:

$ clitest check --obj_name page456 --op_name read
op_name=read
obj_name=page456
check
failed

Because the auditor role has timeout constraint set to 5 (minutes), it was deactivated automatically from the session.

END

Using the py-fortress Command Line Interpreter

The Command Line Interpreter (CLI) drives the admin and review APIs,  allowing ad-hoc RBAC setup and interrogation.  More info in the README.

This document also resides here: README-CLI.

Prerequisites

Completed the setup described: README-QUICKSTART

Getting Started

The command syntax:

cli entity operation --arg1 --arg2 ... 

Where cli executes a package script that maps to this module:

pyfortress.test.cli

The entity is (pick one)

(These are source pointers to their locations in github)

The operation is (pick one):

  • add
  • mod
  • del
  • assign
  • deassign
  • grant
  • revoke
  • read
  • search

(These are just meta tags)

Argument Format

Consists of two dashes ‘- -‘ plus the attribute name and value pair, with a space between them.

--attribute_name value

if an attribute value contains white space,  enclose in single ‘ ‘ or double tics ” “.

--attribute_name 'some value' --attribute_name2 "still more values"

For example, a perm grant:

$ cli perm grant --obj_name myobj --op_name add --role 'my role'

This command invokes Python’s runtime with the program name, cli.py, followed by an entity type, operation name and multiple name-value pairs.

The above used –role is the only argument that isn’t an entity attribute name.  It’s used on user assign, deassign, perm grant, revoke operations.

Arguments as Lists

For multi-occurring attributes, pass in as a list of string values, separated by whitespace

The following arguments are lists

—phones

--phones '+33 401 851 4679' '1-212-251-1111' '(028) 9024 6609'

–mobiles

--mobiles ' 017x-1234567' '+44 020 7234 3456' '1-212-650-9632'

–emails

--emails 'f.lst@somewhere.com' 'myaccount@gmail.com' 'myworkaccount@company.com'

–props

--props 'name1:value1', 'name2:value2', 'name3:value3'

each value contains a name:value pair

Arguments as Constraint

Both the user and role entity support adding temporal constraint.

The following arguments comprise a single constraint

-name :  label for user, i.e uid

--name foo3

For users, this can be any safe text. For role, it must already be passed in, with the role’s name.

–timeout : 99 – set the integer timeout that contains max time (in minutes) that entity may remain inactive.

--timeout 30

30 minutes
–begin_time :  HHMM – determines begin hour entity may be activated.

--begin_time 0900

9:00 am
— end_time :  HHMM – determines end hour when entity is no longer allowed to activate.

--end_time 2359

11:59 pm
–begin_date : YYYYMMDD – determines date when entity may be activated.

--begin_date 20150101

Jan 1, 2015
–end_date :  YYMMDD – indicates latest date entity may be activated.

--end_date 20191231

Dec 31, 2019
–begin_lock_date :  YYYYMMDD – determines beginning of enforced inactive status

--begin_lock_date 20180602

Jun 2, 2018
–end_lock_date : YYMMDD –  end of enforced inactive status.

--end_lock_date 20180610

Jun 10, 2018
–day_mask : 1234567, 1 = Sunday, 2 = Monday, etc – day of week entity may be activated.

--day_mask 1246

Sun, Mon, Wed, Fri

all together

cli user mod --uid someuser --name anysafetext --timeout 30 --begin_time 0900 --end_time 2359 --begin_date 20150101 --end_date 20191231 --begin_lock_date 20180602 --end_lock_date 20180610 --day_mask 1246
cli role add --name manager --description 'manager works 8-5, M-F' --timeout 10 --begin_time 0800 --end_time 1700 --begin_date 20100101 --end_date none --day_mask 1246

A Few Tips More

  • These commands have a one-to-one mapping to the admin and review APIs.  For example, the perm grant command maps to the admin_mgr.grant function and perm search –uid calls review_mgr.user_perms.
  • The description of the commands, including required arguments, can be inferred via the api doc inline to the admin_mgr and review_mgr modules.
  • The program output echos the inputted arguments and the results.

Examples

Two sections,  one each for admin and review commands.  They’re not real-world use cases but include what’s currently working although this code is, how to say it, fresh.  🙂

admin mgr

a. user add

$ cli user add --uid chorowitz --password 'secret' --description 'added with py-fortress cli'
uid=chorowitz
description=added with py-fortress cli
user add
success

b. user mod

$ cli user mod --uid chorowitz --l my location --ou my-ou --department_number 123
uid=chorowitz
department_number=123
l=my location
ou=my-ou
user mod
success

c. user del

$ cli user del --uid chorowitz
uid=chorowitz
user del
success

d. user assign

$ cli user assign --uid chorowitz --role account-mgr
uid=chorowitz
role name=account-mgr
user assign
success

e. user deassign

$ cli user deassign --uid chorowitz --role account-mgr
uid=chorowitz
role name=account-mgr
user deassign
success

f. role add

$ cli role add --name account-mgr
name=account-mgr
role add
success

g. role mod

$ cli role mod --name account-mgr --description 'this desc is optional'
description=cli test role
name=account-mgr
role mod
success

h. role del

$ cli role del --name account-mgr
name=account-mgr
role del
success

i. object add

$ cli object add --obj_name page456
obj_name=page456
object add
success

j. object mod

$ cli object mod --obj_name page456 --description 'optional arg' --ou 'another optional arg'
obj_name=page456
ou=another optional arg
description=optional arg
object mod
success

k. object del

$ cli object del --obj_name page789
obj_name=page789
object del
success

l. perm add

$ cli perm add --obj_name page456 --op_name read
obj_name=page456
op_name=read
perm add
success

m. perm mod

$ cli perm mod --obj_name page456 --op_name read --description 'useful for human readable perm name'
obj_name=page456
op_name=read
description=useful for human readable perm name
perm mod
success

n. perm del

$ cli perm del --obj_name page456 --op_name search
obj_name=page456
op_name=search
perm del
success

o. perm grant

$ cli perm grant --obj_name page456 --op_name update --role account-mgr
obj_name=page456
op_name=update
role name=account-mgr
perm grant
success

p. perm revoke

$ cli perm revoke --obj_name page456 --op_name update --role account-mgr
obj_name=page456
op_name=update
role name=account-mgr
perm revoke
success

review mgr

a. user read

$ cli user read --uid chorowitz
 uid=chorowitz
 user read
 chorowitz
 uid: chorowitz
 dn: uid=chorowitz,ou=People,dc=example,dc=com 
 roles: ['account-mgr'] 
 ...
 *************** chorowitz *******************
 success

b. user search

 $ cli user search --uid c
 uid=c
 user search
 c*:0
     uid: canders
     dn: uid=canders,ou=People,dc=example,dc=com
     roles: ['csr', 'tester'] 
     ...
 *************** c*:0 *******************
 c*:1
     uid: cedwards
     dn: uid=cedwards,ou=People,dc=example,dc=com
     roles: ['manager', 'trainer'] 
     ...
 *************** c*:1 *******************
 c*:2
     uid: chandler
     dn: uid=chandler,ou=People,dc=example,dc=com
     roles: ['auditor'] 
     ...
 *************** c*:2 *******************
 c*:3
     uid: chorowitz
     dn: uid=chorowitz,ou=People,dc=example,dc=com
     roles: ['account-mgr'] 
     ...
 *************** c*:3 ******************* 
 success

c. role read

 $ cli role read --name account-mgr
 name=account-mgr
 role read
 account-mgr
 dn: cn=account-mgr,ou=Roles,dc=example,dc=com
 members: ['uid=cli-user2,ou=People,dc=example,dc=com', 'uid=chorowitz,ou=People,dc=example,dc=com']
 internal_id: 5c189235-41b5-4e59-9d80-dfd64d16372c
 name: account-mgr
 constraint: <model.constraint.Constraint object at 0x7fc250bd9e10>
 Role Constraint:
     raw: account-mgr$0$$$$$$$
     timeout: 0 
     name: account-mgr
 *************** account-mgr *******************
 success

d. role search

 $ cli role search --name py-
 name=py-
 role search
 py-*:0
     dn: cn=py-role-0,ou=Roles,dc=example,dc=com
     description: py-role-0 Role
     constraint: <model.constraint.Constraint object at 0x7f17e8745f60>
     members: ['uid=py-user-0,ou=People,dc=example,dc=com', 'uid=py-user-1,ou=People,dc=example,dc=com', ... ]
     internal_id: 04b82ce3-974b-4ff5-ad21-b19ecca57722
     name: py-role-0
 *************** py-*:0 *******************
 py-*:1
     dn: cn=py-role-1,ou=Roles,dc=example,dc=com
     description: py-role-1 Role
     constraint: <model.constraint.Constraint object at 0x7f17e8733128>
     members: ['uid=py-user-8,ou=People,dc=example,dc=com', 'uid=py-user-9,ou=People,dc=example,dc=com']
     internal_id: 70524da8-3be6-4372-a606-d8175e2ca63b
     name: py-role-1 
 *************** py-*:1 *******************
 py-*:2
     dn: cn=py-role-2,ou=Roles,dc=example,dc=com
     description: py-role-2 Role
     constraint: <model.constraint.Constraint object at 0x7f17e87332b0>
     members: ['uid=py-user-3,ou=People,dc=example,dc=com', 'uid=py-user-5,ou=People,dc=example,dc=com', 'uid=py-user-7,ou=People,dc=example,dc=com']
     internal_id: d1b9da70-9302-46c3-b21b-0fc45b863155
     name: py-role-2
 *************** py-*:2 *******************
 ...
 success

e. object read

 $ cli object read --obj_name page456
 obj_name=page456
 object read
 page456
 description: optional arg
 dn: ftObjNm=page456,ou=Perms,dc=example,dc=com
 internal_id: 1635cb3b-d5e2-4fcb-b61a-b8e91437e536
 obj_name: page456
 ou: another optional arg
 success

f. object search

 $ cli object search --obj_name page
 obj_name=page
 object search
 page*:0
     props: 
     obj_name: page456
     description: optional arg
     dn: ftObjNm=page456,ou=Perms,dc=example,dc=com
     ou: another optional arg
     internal_id: 1635cb3b-d5e2-4fcb-b61a-b8e91437e536
 page*:1
     obj_name: page123
     description: optional arg
     dn: ftObjNm=page123,ou=Perms,dc=example,dc=com
     ou: another optional arg
     internal_id: a823ef98-7be4-4f49-a805-83bfef5a0dfb
 success

g. perm read

 $ cli perm read --obj_name page456 --op_name read
 op_name=read
 obj_name=page456
 perm read
 page456.read
 internal_id: 0dc55181-968e-4c60-8755-e20fa1ce017d
 dn: ftOpNm=read,ftObjNm=page456,ou=Perms,dc=example,dc=com
 abstract_name: page456.read
 description: useful for human readable perm name
 obj_name: page456
 op_name: read
 success

h. perm search

$ cli perm search --obj_name page
 obj_name=page
 perm search
 page*.*:0
     abstract_name: page456.read
     op_name: read
     internal_id: 0dc55181-968e-4c60-8755-e20fa1ce017d
     obj_name: page456
     dn: ftOpNm=read,ftObjNm=page456,ou=Perms,dc=example,dc=com
     description: useful for human readable perm name
 page*.*:1
     roles: ['account-mgr']
     abstract_name: page456.update
     op_name: update
     internal_id: 626bca86-014b-4186-83a6-a583e39868a1
     obj_name: page456
     dn: ftOpNm=update,ftObjNm=page456,ou=Perms,dc=example,dc=com 
 page*.*:2
     roles: ['account-mgr']
     abstract_name: page456.delete
     op_name: delete
     internal_id: 6c2fa5fc-d7c3-4e85-ba7f-5e514ca4263f
     obj_name: page456
     dn: ftOpNm=delete,ftObjNm=page456,ou=Perms,dc=example,dc=com
 success

i. perm search (by role)

 $ cli perm search --role account-mgr
 perm search
 account-mgr:0
     abstract_name: page456.update 
     obj_name: page456
     op_name: update
     roles: ['account-mgr']
     dn: ftOpNm=update,ftObjNm=page456,ou=Perms,dc=example,dc=com
     internal_id: 626bca86-014b-4186-83a6-a583e39868a1
 account-mgr:1
     abstract_name: page456.delete
     obj_name: page456
     op_name: delete
     roles: ['account-mgr']
     dn: ftOpNm=delete,ftObjNm=page456,ou=Perms,dc=example,dc=com
     internal_id: 6c2fa5fc-d7c3-4e85-ba7f-5e514ca4263f
 success

j. perm search (by user)

 $ cli perm search --uid chorowitz
 perm search
 chorowitz:0
     dn: ftOpNm=update,ftObjNm=page456,ou=Perms,dc=example,dc=com
     internal_id: 626bca86-014b-4186-83a6-a583e39868a1
     roles: ['account-mgr']
     abstract_name: page456.update
     obj_name: page456
     op_name: update
 chorowitz:1
     dn: ftOpNm=delete,ftObjNm=page456,ou=Perms,dc=example,dc=com
     internal_id: 6c2fa5fc-d7c3-4e85-ba7f-5e514ca4263f
     roles: ['account-mgr']
     abstract_name: page456.delete
     obj_name: page456
     op_name: delete
 success

END

Next up, Testing the py-fortress RBAC0 System