A software designer website

Access Control: ACL

Gautier DI FOLCO December 06, 2023 [dev] #haskell #access control #security #draft concepts

Few days ago I was watching again Welcome to Zanzibar a French talk about authorization which was given at XCraft, an event organized early October by the Software Crafters Lyon, which I'm part of.

I realized that I was not really remembering all the authorization schemes, and I thought starting a new series on them could be a good idea.

Let's start with a distinction:

You can perfectly be authenticated but don't deal with permissions, conversely you can authorize action without definite actor.

Let start with Access-Control list (ACL), which is the most basic scheme.

The idea is to determine whether an actor can perform an action on a resource.

Let's take an UNIX-derived Operating System (any GNU/Linux distribution, FreeBSD, OpenBSD, NetBSD, etc.), they use this mechanism, especially for file access.

Let's start with some general types:

data Resource = Motd | Shadow deriving stock (Eq, Ord, Show)

data Actor = Alice | Bob deriving stock (Eq, Ord, Show)

data Action = Read | Write deriving stock (Eq, Ord, Show)

The algorithm is pretty simple:

For any Resource, an Actor should have a set of authorized Action

We can draft an implementation:

canAcl :: AclRules -> Resource -> Actor -> Action -> Bool
canAcl rules resource actor action =
  Set.member action $
    Map.findWithDefault mempty actor $
      Map.findWithDefault mempty resource rules

The main idea is: each time a lookup fail, default on an empty container, so we end-up with an empty set of permission and any permission queried will fail.

To make things simple, I have a type alias as AclRules:

type AclRules = Map.Map Resource (Map.Map Actor (Set.Set Action))

Finally, here are some rules and tests:

let rules =
      Map.fromList
        [ ( Motd,
            Map.fromList
              [ (Alice, Set.fromList [Read]),
                (Bob, Set.fromList [Read, Write])
              ]
          ),
          (Shadow, Map.fromList [(Bob, Set.fromList [Read, Write])])
        ]
forM_
  [ (Alice, Motd, Read, True),
    (Alice, Motd, Write, False),
    (Alice, Shadow, Read, False),
    (Alice, Shadow, Write, False),
    (Bob, Motd, Read, True),
    (Bob, Motd, Write, True),
    (Bob, Shadow, Read, True),
    (Bob, Shadow, Write, True)
  ]
  $ \tc@(actor, resource, action, expected) ->
    it (show tc) $
      canAcl rules resource actor action `shouldBe` expected