Access Control: Context-based Access control

We have started our series with Access Control List (ACL), the good news is that it's the basis for the most of the access control schemes, however they are usually stateless.

The thing is it cannot work in cases the system should be in a certain state to perform some operation, that's the case with firewalls trying to check protocols.

Here come Context-based Access control (CBAC).

The mechanism is the following: each time a new packet come we check the type of packet against the state and if it's one of the allowed transition we can update it.

Let's try with TCP.

As a reminder, TCP works as follows:

  • SYN (handshake)
  • SYN/ACK (handshake)
  • ACK (handshake)
  • Data exchange

We can draft the following types:

data TcpState
  = NotInitialized
  | HandShakeSyned 
  | HandShakeSynedAcked
  | Opened
  deriving stock (Eq, Ord, Enum, Show)

data TcpAction
  = Syn
  | SynAck
  | Ack
  | SendData
  | Close
  deriving stock (Eq, Ord, Enum, Show)

canCbac :: TcpState -> TcpAction -> Maybe TcpState

We can come up with some tests:

    describe "CBAC"
      $ forM_
        [ (NotInitialized, Syn, Just HandShakeSyned),
          (NotInitialized, SynAck, Nothing),
          (NotInitialized, Ack, Nothing),
          (NotInitialized, SendData, Nothing),
          (NotInitialized, Close, Nothing),
          (HandShakeSyned, Syn, Nothing),
          (HandShakeSyned, SynAck, Just HandShakeSynedAcked),
          (HandShakeSyned, Ack, Nothing),
          (HandShakeSyned, SendData, Nothing),
          (HandShakeSyned, Close, Nothing),
          (HandShakeSynedAcked, Syn, Nothing),
          (HandShakeSynedAcked, SynAck, Nothing),
          (HandShakeSynedAcked, Ack, Just Opened),
          (HandShakeSynedAcked, SendData, Nothing),
          (HandShakeSynedAcked, Close, Nothing),
          (Opened, Syn, Nothing),
          (Opened, SynAck, Nothing),
          (Opened, Ack, Nothing),
          (Opened, SendData, Just Opened),
          (Opened, Close, Just NotInitialized)
        ]
      $ \tc@(state, action, expected) ->
        it (show tc) $
          canCbac state action `shouldBe` expected

Finally we have a classic state-machine updater function:

canCbac :: TcpState -> TcpAction -> Maybe TcpState
canCbac state action =
  case state of
    NotInitialized -> Syn `transitionTo` HandShakeSyned
    HandShakeSyned -> SynAck `transitionTo` HandShakeSynedAcked
    HandShakeSynedAcked -> Ack `transitionTo` Opened
    Opened -> SendData `transitionTo` Opened <|> Close `transitionTo` NotInitialized
  where
    transitionTo onAction newState =
      if action == onAction
        then Just newState
        else Nothing