User Authentication
This case study looks at providing a useful set of interfaces (with a reference implementation) for authenticating users against a server for resource access.
Architecture
Attribute-Based Access Control (ABAC)
Capabilites
A capability is a base action that can performed on the system. Such as reading a file. For this example we can use a limited set such as read, write, create, and destroy. But the choice is down to the implementation and the environment that it needs to work within.
For security purposes these capabilities should be decided before the implementation and then enforced via hard coding in software.
Policies
A policy is a set of boolean logic statements that provide the checking mechanisim for authentication. Policies should be cascading so that default statements can be mandated early. Further overriding of these defaults can thus be via specific policies. An example would be mandating all resources as strictly non-readable; then allowing very specific users to read those resources within a specific environment or contextural situation.
Attributes
An attribute can be anything - in our design this is limited to textural information - it can take the form of a simple string tag, a key=value pair, or a blob of json data for instance. All attributes can be used in policy statements.
The attribute forms we will make use of are tag, keyvalue, and ternary.
tag is of a simple textural symbol, if it is present then it exists. keyvalue presents a pair of textural key and value, whilst ternary allows for setting a value as true, unknown, or false. We might implement these forms in golang like thus :-
Raw go file
/* Attributes */
type Attributes map[string]string
/* SetTag */
func (attrs Attributes) SetTag(tag string) {
if tags,exists := attrs["tags"]; !exists {
attrs["tags"] = tag
} else {
attrs["tags"] = strings.Join([]string{tags,tag}," ")
}
}
/* Tag */
func (attrs Attributes) Tag(tag string) bool {
if tags,exists := attrs["tags"]; exists {
for _,t := range strings.Split(tags," ") {
if t == tag {
return true
}
}
}
return false
}
/* SetKeyValue */
func (attrs Attributes) SetKeyValue(key,value string) {
attrs[key] = value
}
/* KeyValue */
func (attrs Attributes) KeyValue(key, def string) string {
if value,exists := attrs[key]; exists {
return value
}
return def
}
type Ternary string
const (
True = Ternary("ternary'true")
Unknown = Ternary("ternary'unknown")
False = Ternary("ternary'false")
)
/* SetTernary */
func (attrs Attributes) SetTernary(key string, value Ternary) {
attrs[key] = string(ternary)
}
/* Ternary */
func (attrs Attributes) Ternary(key string) Ternary {
if value,exists := attrs[key]; exists {
switch Ternary(value) {
case True,Unknown,False:
return Ternary(value)
break
default:
return True
break
}
}
return Unknown
}
/* Form */
func (attrs Attributes) Form(key string) string {
if value,exists := attrs[key]; exists {
switch Ternary(value) {
case True,Unknown,False:
return "ternary"
break
default:
return "keyvalue"
break
}
}
for _,tag := range strings.Split(attrs["tags"]," ") {
if tag == key {
return "tag"
}
}
return "unknown"
}
For this design we will allow for four types: environment, subject, action, and resource. In this way we can also map this design to a role-based access control (RBAC), which allows for more stream-lined client-server interfaces; as well as an easier concept to share with operators.
Environment (as a special attribute)
In this design we will use the environment (which could be a general attribute) as a box inwhich to match policy statements. In this way we can make certain aspects of the design simplier then if we presented the environment as merely another type of attribute. An example would be two different environments that share the same set of policies, for instance workfloor and control. Policies can now be placed into four groups:
policies that have only workfloor environment, that have only control environment, that have both workfloor and control environments, and policies that have neither environnment.
TODO: better set theory here please, probably a diagram.
Subject
This type of attribute refers to the properties of a known user, human or machine operator. This fulfils the capability of classic authentication by allowing a policy statement that includes information of one or more user attributes.
Action
This type of attribute that describe the attempted action.
Resource
This type of attribute describes the accessed object.
Domain Specific Language (DSL)
Examples
Read-Only Example
Raw policy file
policy read-write
## drop all capabilities
drop (cap)
## allow read and write capabilies when all
## other statements match
allow (cap READ) and (cap WRITE)
action is file-access
policy read-only-logs
drop (cap)
allow (cap READ)
action is file-access of type log
The policy syntax is formalised on parsing into a symbolic-expression set. The following is the formalised version of our example.
Raw s-expression file
(policy read-write (
(if (must (
(equal (attr action "operation") "file-access")))
(then (
(drop (cap))
(grant (
(cap READ)
(cap WRITE))))))))
(policy read-only-logs (
(if (must (
(equal (attr action "operation") "file-access")
(equal (attr resource "type") "log")))
(then (
(drop (cap))
(grant (
(cap READ))))))))
You will notice that the formalisation arranges the policy into an if and then statement, this allows the implementation to be direct in it’s useage.
Write-Only Example
Raw policy file
policy write-only
drop (cap)
allow (cap WRITE)
action is file-access
subject must have attribute "logger"
subject must not have attribute "public"
test write-only
as correct
with action as file-access
with empty subject
apply attribute as tag "logger"
so only (cap WRITE)
if failure then break circuit
as incorrect
with action as file-access
with empty subject
apply attribute as tag "logger"
apply attribute as tag "public"
so not (cap)
if failure then break circuit
Cascading Example
Raw policy file
# enforce default to drop all permissions
default policy
drop (cap)
test
so not (cap)
if failure then break circuit
# allow read only permission for file-access actions
policy read-only
## this statement on it's own will drop all
## permissions then allow read
allow only (cap READ)
action is file-access
test
with action as file-access
so only (cap READ)
if failure then break circuit
# the following policies also set the environment
# that they apply to, in this case it's the office
policy write-for-staff in office
allow (cap WRITE)
## where is used as syntax-sugar here
## to make the children statements easier
## to grok.
where
action is file-access
subject must have attribute "staff"
test
as correct
with
action as file-access
empty subject apply attribute as tag "staff"
so (cap READ) and (cap WRITE)
if failure then break circuit
as incorrect
with
action as file-access
empty subject
so (cap READ)
so not (cap WRITE)
if failure then break circuit
policy create-for-admin in office
allow (cap CREATE)
where
subject must have
attribute "staff"
attribute "admin"
## when a user creates a resource we add
## an attribute to it as a specific keyvalue
## type. Read the colon symbol as where
apply attribute to resource as keyvalue :
key is "glenda-can-delete"
### we set the value from the environment
### and enforce the type as ternary value
### : true, unknown, or false. If set
### and contents considered truthy then
### value will be true, else false. If
### not set then the value will be unknown
value is ternary (env GLENDA_ALLOWED)
test
as correct
with
empty subject
apply attribute
as tag "staff"
as tag "admin"
so (cap CREATE)
so resouce has attribute "glenda-can-delete"
with ternary value
if failure then break circuit
as incorrect
with empty subject
so not (cap CREATE)
if failure then break circuit
policy destroy-only-for-glenda in remote-office
allow (cap DESTROY)
where
resource must have attribute "glenda-can-delete"
value must be true
subject must have
attribute "staff"
attribute "username"
value is "glenda"
test
as correct
with
empty resource
apply attribute as ternary "glenda-can-delete"
with value true
empty subject
apply attribute as tag "staff"
apply attribute as keyvalue :
key is "username"
value is "glenda"
so (cap DESTROY)
if failure then break circuit
Full Example
We combine all policy examples and show some useful premable configuration to make the policy more human readable and to prevent unforseen errors.
Full
# this is an example of a complete policy
# configuration
# set the default policy
default policy
drop (cap)
# here we create some useful tokens in
# place of the capability default set.
set token as keyvalue :
key is read
value is (cap READ)
set token as keyvalue :
key is write
value is (cap WRITE)
set token as keyvalue :
key is create
value is (cap CREATE)
set token as keyvalue :
key is destroy
value is (cap DESTROY)
set token as keyvalue :
key is execute
value …
Raw s-expression file
(policy default (then (drop (cap))))
(token (keyvalue read (cap READ)))
(token (keyvalue write (cap WRITE)))
(token (keyvalue create (cap CREATE)))
(token (keyvalue destroy (cap DESTROY)))
(token (keyvalue execute (cap EXECUTE)))
(token (keyvalue all (
(read)
(write)
(create)
(destroy)
(execute))))
(berid (cap))
(policy read-only (
(if (must (
(equal (attr action "operation") "file-access")))
(then (grant read))
(else (audit warning)))))
(policy write-for-staff (
(if (must (
(equal (attr enviroment "location") "office")
(equal (attr action "operation") "file-access")
(has (attr subject "staff"))))
(then (grant write))
(else (audit warning)))))
(policy create-for-admin (
(if (must (
(equal (attr environment "location") "office")
(has (
(attr subject "staff")
(attr subject "admin")))))
(then (
(grant (create))
(resource
(ternary
"glenda-can-delete"
(env GLENDA_ALLOWED)))))
(else (audit warning)))))
(policy destroy-only-for-glenda (
(if (must (
(equal (attr environment "location") "remote-office")
(equal (attr resource "glenda-can-delete") true)
(has (attr subject "staff"))
(equal (attr subject "username") "glenda")))
(then (grant destroy))
(else (audit warning)))))
(policy execute-processes (
(if (must (
(equal (attr environment "location") "office")
(within
(attr envirnoment "time")
(time 08 00)
(time 18 00))
(at-least-1 (
(has (attr action "begin-process"))
(has (attr action "end-process"))))
(has (attr resource "can-execute"))
(has (attr subject "staff")
(attr subject "operations"))))
(then (grant execute))
(else (audit warning)))))
(policy backup (
(if (must (
(equal (attr enviroment "location") "office")
(within
(attr environment "time")
(time 01 00)
(time 03 00))
(at-least-1 (
(equal (attr environment "weekday") "Sunday")
(eqaul (attr environment "weekday") "Tuesday")))
(has (attr action "begin-backup"))
(has (attr resource "dump"))
(has (attr subject "operations"))
(has (attr subject "backups"))))
(then (
(grant read)
(audit log "running backup"))
(else (audit warning)))))
(policy root (
(if (must (
(equal (attr environment "location") "office")
(has (attr subject "root"))))
(then (grant all)))))
Authentication Interface
The authentication interface provides a limited (read small attack surface) API for doing specific actions. This interface is designed to be as simple as possible. A combination of finite state machines and ABAC security for authenticating users with the service.
Generating a token
The authentication interface generates bearer tokens for a client to make use of.
Timestamping data with auth
We can use the authentication interface to generate a timestamped banner with provided user data that is verified and authenticated. This is useful if we want to pass around verified data amongst users.
Admin Interface
An admin interface is needed to oversee the authentication datastore and setup required policies. We can implement this through the same API using a special environment to allow policy modification.
Audit
An audit of all interactions with the authentication interface is required to ensure correct state.
Circuit Breakers
A circuit breaker is useful to ensure correct state and to prevent use whilst in an incorrect (read unsafe) state. With our design we can place a number of circuit breakers in place.
TODO: insert circuit breaker diagram here
Finite State Machine Processes
We make use of finite state machines to describe processes and required interactions that an operator must follow to gain a particular outcome, for example - a user logging in.
Domain Specific Language (DSL)
To ensure well-defined and correct configuration, we make use of a tiny domain specific language for constructing our required state machines. You will notice that it has the same syntax and structure as the DSL for ABAC (above).
Examples
New User Example
This example covers the basics of adding a new user account. We begin at user-unknown as we have no information about the user and this is the default state. To transition from user-unknown to user-account the user must go through the user-new-account process, this process requires an email, username, and passphrase to produce a valid username attribute.
Raw policy file
interface
# beginning state
state user-unknown
begin here
subject must not have attribute "username"
# end state
state user-account
subject must have attribute "username"
# process that needs to be implemented
process user-new-account
required as input "email", "username", and "passphrase"
produces attribute user "username"
# transition between beginning and end states
transition
from user-unknown
to user-account
via user-new-account
User login example
This is a simple login model between two states of session management - has a session or doesn’t. If the user does not have a session then they are logged out (the default state). To change between logged out and logged in, the user must go through the user-authentication process and provide a username and passphrase.
Raw policy file
interface
# beginning state
state user-logged-out
begin here
subject must not have token "session"
state user-logged-in
subject must have token "session" and be valid
process user-authentication
required as input "username" and "passphrase"
produces token "session"
transition
from user-logged-out
to user-logged-in
via user-authentication
transition
from user-logged-in
to user-logged-out
User Attribute Change (passphrase)
A common action for a user is to be able to change their passphrase of their account. In this example we explictly ensure that the logged user has write capabilities for the resource attribute passphrase, if not then the process can never be executed. Also to prevent any mistakes if the user-change-passphrase process errors in any manor any changes will be reverted back.
This is an extermely simple version of a passphrase changing action; generally we would ask for the new passphrase in pairs to ensure correct typing from the user and we would want the current passphrase as well to prevent mistakes.
Raw policy file
interface
# we only have a single state
state user-logged-in
subject must have token "session" and be valid
process user-change-passphrase
required as input "new-passphrase"
subject has (cap write) on attribute "passphrase"
transition
from user-logged-in
to user-logged-in
via user-change-passphrase
on error revert changes
Reference Implementation
Here we place an interactive mithril javascript widget (in the form of cards) that show a working create, auth, login, logout, destroy, attribute amend (includes passphrase changes etc). All these need an audit output stream for the interaction as well as the current state machine. We are focused on the datastore/auth components and NOT the over stuff like passphrase resets.
This is purely an in-memory database; maybe allowing the javascript interface to create a set.
Design Requirements
For each process we need to implement we will need to compose into a policy made up of environment, subject, resource, and action attribute sets which grants capabilities and then we enforce it with the implemention.
- create a new user account
- log the user in
- log the user out
- destroy user account
- change account attributes
Policies
Let’s define the default policy and create a set of capability tokens. Notice that we only define tokens that we need, and then prevent direct use of raw capabilities.
default policy
drop (cap)
set token as keyvalue :
key is read
value is (cap READ)
set token as keyvalue :
key is write
value is (cap WRITE)
set token as keyvalue :
key is create
value is (cap CREATE)
set token as keyvalue :
key is destroy
value is (cap DESTROY)
berid of (cap)
Create a new user account
First we need to map an operator (subject) to the action so that we can implement a datastore for user accounts. We implement this at the HTML/XHR server fulfulling the environment attribute set. The resources are the user accounts.
# allow create capability
# for user accounts
policy create-user-accounts in localhost
allow create
where
environment must have state user-unknown
action is create-account
subject must have attribute "operator"
resource must be "/accounts"
# enforce the interactions with the policy
# with an interface
interface
state user-unknown
begin here
subject must not have attribute "username"
state user-known
subject must have attribute "username"
process create-account
required as input :
"email", "username", and "passphrase"
produces attribute subject "username"
transition
from user-unknown
to user-known
via create-account
User login
Because the HTML/XHR server affectly sits between authorisation server and the client we combine attributes directly from the user and the HTML/XHR server when talking to the authorisation server (we overload the subject attribute set).
policy read-user-accounts in localhost
allow read
where
environment must have state no-session
action is create-session
subject must have
attribute "operator"
attribute "username"
attribute "passphrase"
resource must be "/accounts"
policy create-session in localhost
allow create
where
environment must have state no-session
action is create-session
subject must have
attribute "operator"
attribute "username"
attribute "session"
resource must be "/sessions"
interface
state no-session
begin here
subject must not have attribute "session"
state with-session
subject must have attribute "session"
process create-session
required as input : "username" and "passphrase"
produces attribute subject "session"
transition
from no-session
to with-session
via create-session
User logout
policy delete-user-session in localhost
allow destroy
where
environment must have state with-session
action is destroy-session
subject must have
attribute "operator"
attribute "username"
attribute "session"
resource must be "/sessions"
interface
state with-session
begin here
subject must have attribute "session"
state no-session
subject must not have attribute "session"
process destroy-session
required as input : "session"
transition
from with-session
to no-session
via destroy-session
Destroy user account
policy destroy-user-accounts in localhost
allow destroy
where
enviroment must have state with-session
action is destroy-account
subject must have
attribute "operator"
attribute "username"
resource must be "/accounts"
inteface
state with-session
begin here
subject must have attribute "session"
state no-session
subject must not have attribute "session"
process destroy-account
required as input :
"session" and "passphrase"
transition
from with-session
to no-session
via destroy-account
Change account attributes
policy update-account-attributes in localhost
allow write and read
where
environment must have state with-session
action is set-passphrase or set-colour
subject must have
attribute "operator"
attribute "username"
resource must be "/accounts"
interface
state with-session
begin here
subject must have attribute "session"
process set-passphrase
required as input :
"username", "new-passphrase" and "passphrase"
process set-colour
required as input :
"username" and "colour"
transition
loop with-session
via set-passphrase
transition
loop with-session
via set-colour
Plugins for all modules
This deals with the specifics of each interface implementation, such as the hashing functions etc.