diff --git a/acl/acl.go b/acl/acl.go
new file mode 100644
index 0000000000000000000000000000000000000000..38c75c21da5735b7d797f7bc3ba28aa087faa06a
--- /dev/null
+++ b/acl/acl.go
@@ -0,0 +1,205 @@
+package acl
+
+import (
+	iradix "github.com/hashicorp/go-immutable-radix"
+)
+
+// capabilitySet is a type wrapper to help managing a set of capabilities
+type capabilitySet map[string]struct{}
+
+func (c capabilitySet) Check(k string) bool {
+	_, ok := c[k]
+	return ok
+}
+
+func (c capabilitySet) Set(k string) {
+	c[k] = struct{}{}
+}
+
+func (c capabilitySet) Clear() {
+	for cap := range c {
+		delete(c, cap)
+	}
+}
+
+// ACL object is used to convert a set of policies into a structure that
+// can be efficiently evaluated to determine if an action is allowed.
+type ACL struct {
+	// management tokens are allowed to do anything
+	management bool
+
+	// namespaces maps a namespace to a capabilitySet
+	namespaces *iradix.Tree
+
+	agent    string
+	node     string
+	operator string
+}
+
+// maxPrivilege returns the policy which grants the most privilege
+func maxPrivilege(a, b string) string {
+	switch {
+	case a == PolicyDeny || b == PolicyDeny:
+		return PolicyDeny
+	case a == PolicyWrite || b == PolicyWrite:
+		return PolicyWrite
+	case a == PolicyRead || b == PolicyRead:
+		return PolicyRead
+	default:
+		return ""
+	}
+}
+
+// NewACL compiles a set of policies into an ACL object
+func NewACL(management bool, policies []*Policy) (*ACL, error) {
+	// Hot-path management tokens
+	if management {
+		return &ACL{management: true}, nil
+	}
+
+	// Create the ACL object
+	acl := &ACL{}
+	nsTxn := iradix.New().Txn()
+
+	for _, policy := range policies {
+	NAMESPACES:
+		for _, ns := range policy.Namespaces {
+			// Check for existing capabilities
+			var capabilities capabilitySet
+			raw, ok := nsTxn.Get([]byte(ns.Name))
+			if ok {
+				capabilities = raw.(capabilitySet)
+			} else {
+				capabilities = make(capabilitySet)
+				nsTxn.Insert([]byte(ns.Name), capabilities)
+			}
+
+			// Deny always takes precedence
+			if capabilities.Check(NamespaceCapabilityDeny) {
+				continue NAMESPACES
+			}
+
+			// Add in all the capabilities
+			for _, cap := range ns.Capabilities {
+				if cap == NamespaceCapabilityDeny {
+					// Overwrite any existing capabilities
+					capabilities.Clear()
+					capabilities.Set(NamespaceCapabilityDeny)
+					continue NAMESPACES
+				}
+				capabilities.Set(cap)
+			}
+		}
+
+		// Take the maximum privilege for agent, node, and operator
+		if policy.Agent != nil {
+			acl.agent = maxPrivilege(acl.agent, policy.Agent.Policy)
+		}
+		if policy.Node != nil {
+			acl.node = maxPrivilege(acl.node, policy.Node.Policy)
+		}
+		if policy.Operator != nil {
+			acl.operator = maxPrivilege(acl.operator, policy.Operator.Policy)
+		}
+	}
+
+	// Finalize the namespaces
+	acl.namespaces = nsTxn.Commit()
+	return acl, nil
+}
+
+// AllowNamespaceOperation checks if a given operation is allowed for a namespace
+func (a *ACL) AllowNamespaceOperation(ns string, op string) bool {
+	// Hot path management tokens
+	if a.management {
+		return true
+	}
+
+	// Check for a matching capability set
+	raw, ok := a.namespaces.Get([]byte(ns))
+	if !ok {
+		return false
+	}
+
+	// Check if the capability has been granted
+	capabilities := raw.(capabilitySet)
+	return capabilities.Check(op)
+}
+
+// AllowAgentRead checks if read operations are allowed for an agent
+func (a *ACL) AllowAgentRead() bool {
+	switch {
+	case a.management:
+		return true
+	case a.agent == PolicyWrite:
+		return true
+	case a.agent == PolicyRead:
+		return true
+	default:
+		return false
+	}
+}
+
+// AllowAgentWrite checks if write operations are allowed for an agent
+func (a *ACL) AllowAgentWrite() bool {
+	switch {
+	case a.management:
+		return true
+	case a.agent == PolicyWrite:
+		return true
+	default:
+		return false
+	}
+}
+
+// AllowNodeRead checks if read operations are allowed for a node
+func (a *ACL) AllowNodeRead() bool {
+	switch {
+	case a.management:
+		return true
+	case a.node == PolicyWrite:
+		return true
+	case a.node == PolicyRead:
+		return true
+	default:
+		return false
+	}
+}
+
+// AllowNodeWrite checks if write operations are allowed for a node
+func (a *ACL) AllowNodeWrite() bool {
+	switch {
+	case a.management:
+		return true
+	case a.node == PolicyWrite:
+		return true
+	default:
+		return false
+	}
+}
+
+// AllowOperatorRead checks if read operations are allowed for a operator
+func (a *ACL) AllowOperatorRead() bool {
+	switch {
+	case a.management:
+		return true
+	case a.operator == PolicyWrite:
+		return true
+	case a.operator == PolicyRead:
+		return true
+	default:
+		return false
+	}
+}
+
+// AllowOperatorWrite checks if write operations are allowed for a operator
+func (a *ACL) AllowOperatorWrite() bool {
+	switch {
+	case a.management:
+		return true
+	case a.operator == PolicyWrite:
+		return true
+	default:
+		return false
+	}
+}
diff --git a/acl/acl_test.go b/acl/acl_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..32680476901cb693056daa525cfb7c88edce37b3
--- /dev/null
+++ b/acl/acl_test.go
@@ -0,0 +1,193 @@
+package acl
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCapabilitySet(t *testing.T) {
+	var cs capabilitySet = make(map[string]struct{})
+
+	// Check no capabilities by default
+	if cs.Check(PolicyDeny) {
+		t.Fatalf("unexpected check")
+	}
+
+	// Do a set and check
+	cs.Set(PolicyDeny)
+	if !cs.Check(PolicyDeny) {
+		t.Fatalf("missing check")
+	}
+
+	// Clear and check
+	cs.Clear()
+	if cs.Check(PolicyDeny) {
+		t.Fatalf("unexpected check")
+	}
+}
+
+func TestMaxPrivilege(t *testing.T) {
+	type tcase struct {
+		Privilege      string
+		PrecedenceOver []string
+	}
+	tcases := []tcase{
+		{
+			PolicyDeny,
+			[]string{PolicyDeny, PolicyWrite, PolicyRead, ""},
+		},
+		{
+			PolicyWrite,
+			[]string{PolicyWrite, PolicyRead, ""},
+		},
+		{
+			PolicyRead,
+			[]string{PolicyRead, ""},
+		},
+	}
+
+	for idx1, tc := range tcases {
+		for idx2, po := range tc.PrecedenceOver {
+			if maxPrivilege(tc.Privilege, po) != tc.Privilege {
+				t.Fatalf("failed %d %d", idx1, idx2)
+			}
+			if maxPrivilege(po, tc.Privilege) != tc.Privilege {
+				t.Fatalf("failed %d %d", idx1, idx2)
+			}
+		}
+	}
+}
+
+func TestACLManagement(t *testing.T) {
+	// Create management ACL
+	acl, err := NewACL(true, nil)
+	assert.Nil(t, err)
+
+	// Check default namespace rights
+	assert.Equal(t, true, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
+	assert.Equal(t, true, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
+
+	// Check non-specified namespace
+	assert.Equal(t, true, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
+
+	// Check the other simpler operations
+	assert.Equal(t, true, acl.AllowAgentRead())
+	assert.Equal(t, true, acl.AllowAgentWrite())
+	assert.Equal(t, true, acl.AllowNodeRead())
+	assert.Equal(t, true, acl.AllowNodeWrite())
+	assert.Equal(t, true, acl.AllowOperatorRead())
+	assert.Equal(t, true, acl.AllowOperatorWrite())
+}
+
+func TestACLMerge(t *testing.T) {
+	// Merge read + write policy
+	p1, err := Parse(readAll)
+	assert.Nil(t, err)
+	p2, err := Parse(writeAll)
+	assert.Nil(t, err)
+	acl, err := NewACL(false, []*Policy{p1, p2})
+	assert.Nil(t, err)
+
+	// Check default namespace rights
+	assert.Equal(t, true, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
+	assert.Equal(t, true, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
+
+	// Check non-specified namespace
+	assert.Equal(t, false, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
+
+	// Check the other simpler operations
+	assert.Equal(t, true, acl.AllowAgentRead())
+	assert.Equal(t, true, acl.AllowAgentWrite())
+	assert.Equal(t, true, acl.AllowNodeRead())
+	assert.Equal(t, true, acl.AllowNodeWrite())
+	assert.Equal(t, true, acl.AllowOperatorRead())
+	assert.Equal(t, true, acl.AllowOperatorWrite())
+
+	// Merge read + blank
+	p3, err := Parse("")
+	assert.Nil(t, err)
+	acl, err = NewACL(false, []*Policy{p1, p3})
+	assert.Nil(t, err)
+
+	// Check default namespace rights
+	assert.Equal(t, true, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
+	assert.Equal(t, false, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
+
+	// Check non-specified namespace
+	assert.Equal(t, false, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
+
+	// Check the other simpler operations
+	assert.Equal(t, true, acl.AllowAgentRead())
+	assert.Equal(t, false, acl.AllowAgentWrite())
+	assert.Equal(t, true, acl.AllowNodeRead())
+	assert.Equal(t, false, acl.AllowNodeWrite())
+	assert.Equal(t, true, acl.AllowOperatorRead())
+	assert.Equal(t, false, acl.AllowOperatorWrite())
+
+	// Merge read + deny
+	p4, err := Parse(denyAll)
+	assert.Nil(t, err)
+	acl, err = NewACL(false, []*Policy{p1, p4})
+	assert.Nil(t, err)
+
+	// Check default namespace rights
+	assert.Equal(t, false, acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs))
+	assert.Equal(t, false, acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob))
+
+	// Check non-specified namespace
+	assert.Equal(t, false, acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs))
+
+	// Check the other simpler operations
+	assert.Equal(t, false, acl.AllowAgentRead())
+	assert.Equal(t, false, acl.AllowAgentWrite())
+	assert.Equal(t, false, acl.AllowNodeRead())
+	assert.Equal(t, false, acl.AllowNodeWrite())
+	assert.Equal(t, false, acl.AllowOperatorRead())
+	assert.Equal(t, false, acl.AllowOperatorWrite())
+}
+
+var readAll = `
+namespace "default" {
+	policy = "read"
+}
+agent {
+	policy = "read"
+}
+node {
+	policy = "read"
+}
+operator {
+	policy = "read"
+}
+`
+
+var writeAll = `
+namespace "default" {
+	policy = "write"
+}
+agent {
+	policy = "write"
+}
+node {
+	policy = "write"
+}
+operator {
+	policy = "write"
+}
+`
+
+var denyAll = `
+namespace "default" {
+	policy = "deny"
+}
+agent {
+	policy = "deny"
+}
+node {
+	policy = "deny"
+}
+operator {
+	policy = "deny"
+}
+`