Access Control: Attribute-based Access control

Previously we have seen Role-based Access control, we have seen it has two shortcomings:

  • We have to create one role per access
  • We have to dynamically assign roles if we want to deal with ownership

Here comes Attribute-based Access control (ABAC).

The idea is simple, instead of an identifier (a role or a subject) and a target (a resource), we give a set of attributes (resource/role/subject/context information) and an expression rule.

Note: the context can be a lot of things such as time of system state.

Note 2 : it is the mechanism used in AWS Identity and Access Management (IAM) policy definition

Let's start with some types:

data Attribute
  = Role Role
  | Actor Actor
  | Resource Resource
  | Action Action
  | Arbitrary Text Text
  deriving stock (Eq, Ord, Show)

data AbacRules
  = Or AbacRules AbacRules
  | And AbacRules AbacRules
  | Has Attribute
  | Match (Attribute -> Attribute -> Bool)

canAbac :: AbacRules -> Set.Set Attribute -> Bool

Match our trick to deal with ownership as it helps us to zip the attributes-set on it-self, so we can define it as:

isOwner :: AbacRules
isOwner =
  Match $ \x y ->
    case (x, y) of
      (Actor actor, Resource (FileOwnBy owner)) -> actor == owner
      _ -> False

We can exercise it with some tests:

  describe "ABAC" $ do
    let rules =
          Has (Role Admin)
            `Or` isOwner
            `Or` (Has (Resource Motd) `And` Has (Action Read))
    forM_
      [ ([Resource Motd, Action Read, Actor Alice, Role SimpleUser], True),
        ([Resource Motd, Action Write, Actor Alice, Role SimpleUser], False),
        ([Resource Motd, Action Write, Actor Bob, Role Admin], True),
        ([Resource (FileOwnBy Alice), Actor Alice], True),
        ([Resource (FileOwnBy Alice), Actor Charlie], False)
      ]
      $ \tc@(attributes, expected) ->
        it (show tc) $
          canAbac rules (Set.fromList attributes) `shouldBe` expected

And finally the implementation:

canAbac :: AbacRules -> Set.Set Attribute -> Bool
canAbac rules attributes =
  case rules of
    Or x y -> canAbac x attributes || canAbac y attributes
    And x y -> canAbac x attributes && canAbac y attributes
    Has x -> Set.member x attributes
    Match p -> or [p x y | x <- Set.toList attributes, y <- Set.toList attributes]