An implementation of a distributed access-control server that is based on Google Zanzibar

access-controller

Latest Release Go Report Card Slack

An implementation of a distributed access-control server that is based on Google Zanzibar - "Google's Consistent, Global Authorization System".

An instance of an access-controller is similar to the aclserver implementation called out in the paper. A cluster of access-controllers implement the functional equivalent of the Zanzibar aclserver cluster.

Getting Started

If you want to setup an instance of the Authorizer platform as a whole, browse the API References, or just brush up on the concepts and design of the platform, take a look at the official platform documentation. If you're only interested in running the access-controller then continue on.

Setup a Cluster

An access-controller server supports single node or multi-node (clustered) topologies. Instructions for running the server with these topologies are outlined below.

To gain the benefits of the distributed query model that the access-controller implements, it is recommend to run a large cluster. Doing so will help distribute query load across more nodes within the cluster. The underlying cluster membership list is based on Hashicorp's memberlist

a library that manages cluster membership and member failure detection using a gossip based protocol.

A cluster should be able to suport hundreds of nodes. If you find otherwise, please submit an issue.

Docker Compose

docker-compose.yml provides an example of how to setup a multi-node cluster using Docker and is a great way to get started quickly.

$ docker compose -f docker/docker-compose.yml up

Kubernetes (Recommended)

Take a look at our official Helm chart.

Pre-compiled Binaries

Download the latest release and extract it.

Pre-requisites

To run an access-controller you must have a running CockroachDB database. Take a look at setting up CockroachDB with Docker.

Single Node

$ ./bin/access-controller

Multi-node

Start a multi-node cluster by starting multiple independent servers and use the -join flag to join the node to an existing cluster.

$ ./bin/access-controller -node-port 7946 -grpc-port 50052
$ ./bin/access-controller -node-port 7947 -grpc-port 50053 -join 127.0.0.1:7946
$ ./bin/access-controller -node-port 7948 -grpc-port 50054 -join 127.0.0.1:7947

Next Steps...

Take a look at the examples of how to:

Don't hesitate to browse the official Documentation, API Reference and Examples.

Community

The access-controller is an open-source project and we value and welcome new contributors and members of the community. Here are ways to get in touch with the community:

Comments
  • bug:Namespace config store/get bug make check fail while crossing namespaces.

    bug:Namespace config store/get bug make check fail while crossing namespaces.

    Is your feature request related to a problem? Please describe.

    Namespace config store/get bug make check fail while crossing namespaces.

    // make charles a member of the auth0/iam team insert tuple:

    { "relationTupleDeltas": [ { "action": "ACTION_INSERT", "relationTuple": { "namespace": "github_team", "object": "auth0/iam", "relation": "member", "subject": { "id": "[email protected]" } } } ] }

    // make members of auth0/iam team admins of auth0/express-jwt { "relationTupleDeltas": [ { "action": "ACTION_INSERT", "relationTuple": { "namespace": "github_repo", "object": "auth0/express-jwt", "relation": "repo_admin", "subject": { "set": { "namespace": "github_team", "object": "auth0/iam", "relation": "member" } } } } ] }

    // check("github_repo:auth0/express-jwt", "repo_admin", "[email protected]"); check request:

    { "namespace": "github_repo", "object": "auth0/express-jwt", "relation": "repo_admin", "subject": { "id": "[email protected]" } }

    response should be : { "allowed": true }

    but { "allowed":false }

    related code

    nsConfigSnapshotTimestampKey should be different per namespace

    ` if !ok { snapshot, err := a.chooseNamespaceConfigSnapshot(namespace) if err != nil { if err == ErrNoLocalNamespacesDefined { return false, NamespaceConfigError{ Message: fmt.Sprintf("'%s' namespace is undefined. If you recently added it, it may take a couple minutes to propagate", namespace), Type: NamespaceDoesntExist, }.ToStatus().Err() }

    		return false, internalErrorStatus
    	}
    
    	snapshotTimestamp = snapshot.Timestamp
    
    	ctx = NewContextWithNamespaceConfigTimestamp(ctx, snapshotTimestamp)
    } else {
    	snapshotTimestamp = peerNamespaceCfgTs
    }
    
    cfg, err := a.PeerNamespaceConfigStore.GetNamespaceConfigSnapshot(a.ServerID, snapshotTimestamp)
    if err != nil {
    	return false, internalErrorStatus
    }
    
    if cfg == nil {
    	return false, NamespaceConfigError{
    		Message: fmt.Sprintf("'%s' namespace is undefined. If you recently added it, it may take a couple minutes to propagate", namespace),
    		Type:    NamespaceDoesntExist,
    	}.ToStatus().Err()
    }
    

    `

    Describe the solution you'd like

    namespace should be considered be a part of key when storing/loading snapshots

    ` // NewContextWithNamespaceConfigTimestamp returns a new Context that carries the namespace config timestamp. func NewContextWithNamespaceConfigTimestamp(ctx context.Context, namespace string, timestamp time.Time) context.Context { return context.WithValue(ctx, fmt.Sprintf("%s%d", namespace, nsConfigSnapshotTimestampKey), timestamp) }

    // NamespaceConfigTimestampFromContext extracts the snapshot timestamp for a namespace // configuration from the provided context. If none is present, a boolean false is returned. func NamespaceConfigTimestampFromContext(ctx context.Context, namespace string) (time.Time, bool) { timestamp, ok := ctx.Value(fmt.Sprintf("%s%d", namespace, nsConfigSnapshotTimestampKey)).(time.Time) return timestamp, ok

    if !ok {
    	snapshot, err := a.chooseNamespaceConfigSnapshot(namespace)
    	if err != nil {
    		if err == ErrNoLocalNamespacesDefined {
    			return false, NamespaceConfigError{
    				Message: fmt.Sprintf("'%s' namespace is undefined. If you recently added it, it may take a couple minutes to propagate", namespace),
    				Type:    NamespaceDoesntExist,
    			}.ToStatus().Err()
    		}
    
    		return false, internalErrorStatus
    	}
    
    	snapshotTimestamp = snapshot.Timestamp
    
    	ctx = NewContextWithNamespaceConfigTimestamp(ctx, **namespace**, snapshotTimestamp)
    } else {
    	snapshotTimestamp = peerNamespaceCfgTs
    }
    
    cfg, err := a.PeerNamespaceConfigStore.GetNamespaceConfigSnapshot(a.ServerID, **namespace**, snapshotTimestamp)
    if err != nil {
    	return false, internalErrorStatus
    }
    
    if cfg == nil {
    	return false, NamespaceConfigError{
    		Message: fmt.Sprintf("'%s' namespace is undefined. If you recently added it, it may take a couple minutes to propagate", namespace),
    		Type:    NamespaceDoesntExist,
    	}.ToStatus().Err()
    }
    

    `

    Describe alternatives you've considered

  • SubjectSet advanced rewrites edge case

    SubjectSet advanced rewrites edge case

    Is your feature request related to a problem? Please describe.

    I've been testing a more advanced scenario for specifying SubjectSet rewrites, basically combining concentric relationships on a single namespace with hierarchical permissions between two related namespaces, and ran into a edge case. The problem is that the relation tuples of the inner/nested namespace are not being honored (or perhaps I'm not using correct syntax for rewrite config). Here's a repro:

    1. Configure namespaces (files and folders with reader and writer relations)
    $ grpcurl -plaintext -d \
    '{
        "config": {
            "name": "folders",
            "relations": [
                {
                    "name": "..."
                },
                {
                    "name": "writer"
                },
                {
                	"name": "reader",
                	"rewrite": {
                        "union": {
                            "children": [
                                { "this": {} },
                                {
                                  	"computedSubjectset": { "relation": "writer" }
                                }
                            ]
                        }
                    }
                }
            ]
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.NamespaceConfigService.WriteConfig
    {
    
    }
    $ grpcurl -plaintext -d \
    '{
        "config": {
            "name": "files",
            "relations": [
                {
                    "name": "parent"
                },
                {
                    "name": "writer",
                    "rewrite": {
                        "union": {
                            "children": [
                                {
                                    "tupleToSubjectset": {
                                        "tupleset": { "relation": "parent" },
                                        "computedSubjectset": { "relation": "writer" }
                                    }
                                }
                            ]
                        }
                    }
                },
                {
                    "name": "reader",
                    "rewrite": {
                        "union": {
                            "children": [
                                { "this": {} },
                                {
                                  	"computedSubjectset": { "relation": "writer" }
                                },
                                {
                                    "tupleToSubjectset": {
                                        "tupleset": { "relation": "parent" },
                                        "computedSubjectset": { "relation": "reader" }
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.NamespaceConfigService.WriteConfig
    {
    
    }
    
    1. Insert a couple of relation tuples
    $ grpcurl -plaintext -d \
    '{
        "relationTupleDeltas": [
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "files",
                    "object": "file1",
                    "relation": "parent",
                    "subject": {
                        "set": {
                            "namespace": "folders",
                            "object": "folder1",
                            "relation": "..."
                        }
                    }
                }
            },
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "folders",
                    "object": "folder1",
                    "relation": "writer",
                    "subject": {
                        "id": "edmund"
                    }
                }
            },
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "files",
                    "object": "file1",
                    "relation": "writer",
                    "subject": {
                        "id": "baldrick"
                    }
                }
            }
        ]
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.WriteService.WriteRelationTuplesTxn
    {
    
    }
    
    1. Check for permissions
    $ grpcurl -plaintext -d \
    '{
        "namespace": "folders",
        "object": "folder1",
        "relation": "writer",
        "subject": {
            "id": "edmund"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
      "allowed": true
    }  # OK - edmund is a writer on folder1
    $ grpcurl -plaintext -d \
    '{
        "namespace": "folders",
        "object": "folder1",
        "relation": "reader",
        "subject": {
            "id": "edmund"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
      "allowed": true
    }  # OK - edmund is a reader on folder1 since he is a writer on this folder too
    $ grpcurl -plaintext -d \
    '{
        "namespace": "files",
        "object": "file1",
        "relation": "writer",
        "subject": {
            "id": "edmund"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
      "allowed": true
    }  # OK - edmund is a writer on file1 since he is a writer on the parent folder
    $ grpcurl -plaintext -d \
    '{
        "namespace": "files",
        "object": "file1",
        "relation": "reader",
        "subject": {
            "id": "edmund"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
      "allowed": true
    }  # OK - edmund is a reader on file1 since he is a writer on the parent folder
    $ grpcurl -plaintext -d \
    '{
        "namespace": "files",
        "object": "file1",
        "relation": "writer",
        "subject": {
            "id": "baldrick"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
    
    }  # NOT OK - baldrick is a writer on file1
    $ grpcurl -plaintext -d \
    '{
        "namespace": "files",
        "object": "file1",
        "relation": "reader",
        "subject": {
            "id": "baldrick"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
    
    }  # NOT OK - baldrick is a reader on file1 since he is a writer too
    

    Describe the solution you'd like The inserted relation tuples on the inner/nested namespace should be correctly reflected through check calls.

  • bug: The ... relation (alias relation) is not implicitly defined

    bug: The ... relation (alias relation) is not implicitly defined

    Is your feature request related to a problem? Please describe.

    The SubjectSet rewrites example is not working since the ... relation (alias relation) is not implicitly defined as stated in the docs. Example:

    $ grpcurl -plaintext -d \
    '{
        "config": {
            "name": "projects",
            "relations": [
                {
                    "name": "viewer"
                }
            ]
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.NamespaceConfigService.WriteConfig
    {
    
    }
    $ grpcurl -plaintext -d \
    '{
        "config": {
            "name": "tasks",
            "relations": [
                {
                    "name": "parent"
                },
                {
                    "name": "viewer",
                    "rewrite": {
                        "union": {
                            "children": [
                                {
                                    "tupleToSubjectset": {
                                        "tupleset": { "relation": "parent" },
                                        "computedSubjectset": { "relation": "viewer" }
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.NamespaceConfigService.WriteConfig
    {
    
    }
    $ grpcurl -plaintext -d \
    '{
        "relationTupleDeltas": [
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "tasks",
                    "object": "task1",
                    "relation": "parent",
                    "subject": {
                        "set": {
                            "namespace": "projects",
                            "object": "project1",
                            "relation": "..."
                        }
                    }
                }
            }
        ]
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.WriteService.WriteRelationTuplesTxn
    ERROR:
      Code: InvalidArgument
      Message: SubjectSet 'projects:project1#...' references relation '...' which is undefined in the namespace 'projects' at snapshot config timestamp '2021-08-30 12:55:24.160712 +0000 UTC'. If this relation was recently added to the config, please try again in a couple minutes
    

    Describe alternatives you've considered

    Once I add the ... relation explicitly the rewrite example works:

    $ grpcurl -plaintext -d \
    '{
        "config": {
            "name": "projects",
            "relations": [
                {
                        "name": "viewer"
                },
                {
                        "name": "..."
                }
            ]
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.NamespaceConfigService.WriteConfig
    {
    
    }
    $ grpcurl -plaintext -d \
    '{
        "relationTupleDeltas": [
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "tasks",
                    "object": "task1",
                    "relation": "parent",
                    "subject": {
                        "set": {
                            "namespace": "projects",
                            "object": "project1",
                            "relation": "..."
                        }
                    }
                }
            },
            {
                "action": "ACTION_INSERT",
                "relationTuple": {
                    "namespace": "projects",
                    "object": "project1",
                    "relation": "viewer",
                    "subject": {
                        "id": "gruuya"
                    }
                }
            }
        ]
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.WriteService.WriteRelationTuplesTxn
    {
    
    }
    $ grpcurl -plaintext -d \
    '{
        "namespace": "tasks",
        "object": "task1",
        "relation": "viewer",
        "subject": {
            "id": "gruuya"
        }
     }' localhost:50052 authorizer.accesscontroller.v1alpha1.CheckService.Check
    {
      "allowed": true
    }
    
  • fix: resolve namespace cfg snapshot timestamps uniquly per namespace

    fix: resolve namespace cfg snapshot timestamps uniquly per namespace

    The changes herein ensure that namespace config snapshots are resolved and propagated uniquely per namespace. Namespace config changes can happen at different times between any two different namespaces, and therefore we must propagate the snapshot timestamps independently in the request context.

    Fixes #36 .

  • ci: add github 'review' action to vet, lint, and test code.

    ci: add github 'review' action to vet, lint, and test code.

    The changes herein add a new 'review' workflow for pull request review checks. The workflow consists of two jobs, one for Go tests and one for protobuf definition breaking change checks.

  • ci: add GitHub Workflows for post-merge and releasing after a tag

    ci: add GitHub Workflows for post-merge and releasing after a tag

    The changes herein add two new GitHub Workflows. The first runs code coverage checks on the master branch when changes are pushed to it. The second performs a release when a semver tag is pushed.

    closes #12

  • fix: disallow references to undefined relations and misc

    fix: disallow references to undefined relations and misc

    The changes herein disallow references in relation tuples to undefined relations in namespace configs. If a write is issued that references a relation that is undefined at that particular snapshot in time, an error is returned. Other changes include exponential retry-backoff for namespace monitoring and more appropriate error status responses.

    closes #8 closes #14 closes #15

  • feat: validate relation tuple writes

    feat: validate relation tuple writes

    The purpose of this feature is to ensure that relation tuple writes don't reference non-existing namespaces or relations within a namespace. Relations or SubjectSets should not be permitted as values unless there is a corresponding namespace config or relation config for the referenced entities.

    If an invalid relation or namespace is referenced, an error should be returned appropriately.

  • test: create a suite of tests and get coverage up to > 90%

    test: create a suite of tests and get coverage up to > 90%

    The project is lacking a lot of tests at this point in time. Most of the testing has been done manually. The purpose of this issue is to enhance the test suite and improve upon existing test coverage.

  • feat: create relation tuple tables with added namespace config

    feat: create relation tuple tables with added namespace config

    The changes herein add support to automatically provision a new table to store the relation tuples for a namespace when the namespace is first created.

    Closes #2

  • feat: add Lookup API to lookup the relations a subject has to one or more objects

    feat: add Lookup API to lookup the relations a subject has to one or more objects

    Given one or more relations and a subject, the goal of the Lookup API is to determine the object(s) for which the user has the given relation(s). The query should resolve by starting at the subject node in the Graph of Relations and doing a reverse traversal until all of the object nodes of the graph have been reached from the initial subject node.

  • feat: add support for snapshot tokens (e.g. zookies)

    feat: add support for snapshot tokens (e.g. zookies)

    Snapshot tokens (referred to as zookies in the paper in section 2.2) should be implemented to avoid evaluating checks for new contents using stale ACLs. The purpose of this work is to support the content-change check protocol and consistency model inspired by Zanzibar.

Distributed lock manager. Warning: very hard to use it properly. Not because it's broken, but because distributed systems are hard. If in doubt, do not use this.

What Dlock is a distributed lock manager [1]. It is designed after flock utility but for multiple machines. When client disconnects, all his locks are

Dec 24, 2019
Distributed reliable key-value store for the most critical data of a distributed system

etcd Note: The main branch may be in an unstable or even broken state during development. For stable versions, see releases. etcd is a distributed rel

Dec 30, 2022
Simplified distributed locking implementation using Redis

redislock Simplified distributed locking implementation using Redis. For more information, please see examples. Examples import ( "fmt" "time"

Dec 24, 2022
Golang implementation of distributed mutex on Azure lease blobs

Distributed Mutex on Azure Lease Blobs This package implements distributed lock available for multiple processes. Possible use-cases include exclusive

Jul 31, 2022
implementation of some distributed system techniques

Distributed Systems These applications were built with the objective of studding a distributed systems using the most recent technics. The main ideia

Feb 18, 2022
An implementation of a distributed KV store backed by Raft tolerant of node failures and network partitions 🚣
An implementation of a distributed KV store backed by Raft tolerant of node failures and network partitions 🚣

barge A simple implementation of a consistent, distributed Key:Value store which uses the Raft Concensus Algorithm. This project launches a cluster of

Nov 24, 2021
Lockgate is a cross-platform locking library for Go with distributed locks using Kubernetes or lockgate HTTP lock server as well as the OS file locks support.

Lockgate Lockgate is a locking library for Go. Classical interface: 2 types of locks: shared and exclusive; 2 modes of locking: blocking and non-block

Dec 16, 2022
A distributed system for embedding-based retrieval
A distributed system for embedding-based retrieval

Overview Vearch is a scalable distributed system for efficient similarity search of deep learning vectors. Architecture Data Model space, documents, v

Dec 30, 2022
Golimit is Uber ringpop based distributed and decentralized rate limiter
Golimit is Uber ringpop based distributed and decentralized rate limiter

Golimit A Distributed Rate limiter Golimit is Uber ringpop based distributed and decentralized rate limiter. It is horizontally scalable and is based

Dec 21, 2022
Distributed disk storage database based on Raft and Redis protocol.
Distributed disk storage database based on Raft and Redis protocol.

IceFireDB Distributed disk storage system based on Raft and RESP protocol. High performance Distributed consistency Reliable LSM disk storage Cold and

Dec 31, 2022
Take control of your data, connect with anything, and expose it anywhere through protocols such as HTTP, GraphQL, and gRPC.
Take control of your data, connect with anything, and expose it anywhere through protocols such as HTTP, GraphQL, and gRPC.

Semaphore Chat: Discord Documentation: Github pages Go package documentation: GoDev Take control of your data, connect with anything, and expose it an

Sep 26, 2022
A revamped Google's jump consistent hash

Overview This is a revamped Google's jump consistent hash. It overcomes the shortcoming of the original implementation - not being able to remove node

Dec 16, 2022
The Go language implementation of gRPC. HTTP/2 based RPC

gRPC-Go The Go implementation of gRPC: A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. For more information

Jan 7, 2023
distributed data sync with operational transformation/transforms

DOT The DOT project is a blend of operational transformation, CmRDT, persistent/immutable datastructures and reactive stream processing. This is an im

Dec 16, 2022
High performance, distributed and low latency publish-subscribe platform.
High performance, distributed and low latency publish-subscribe platform.

Emitter: Distributed Publish-Subscribe Platform Emitter is a distributed, scalable and fault-tolerant publish-subscribe platform built with MQTT proto

Jan 2, 2023
Fast, efficient, and scalable distributed map/reduce system, DAG execution, in memory or on disk, written in pure Go, runs standalone or distributedly.

Gleam Gleam is a high performance and efficient distributed execution system, and also simple, generic, flexible and easy to customize. Gleam is built

Jan 1, 2023
Go Micro is a framework for distributed systems development

Go Micro Go Micro is a framework for distributed systems development. Overview Go Micro provides the core requirements for distributed systems develop

Jan 8, 2023
A distributed lock service in Go using etcd

locker A distributed lock service client for etcd. What? Why? A distributed lock service is somewhat self-explanatory. Locking (mutexes) as a service

Sep 27, 2022