Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ services:
AWS_ACCESS_KEY_ID: foo
AWS_SECRET_ACCESS_KEY: bar
TABLE_NAME: OpaDynamodbIntegrationTest
ports:
- 8001:8001
depends_on:
- dynamodb
links:
- dynamodb:dynamodb
command: tail -f /dev/null
dynamodb-admin:
image: aaronshaf/dynamodb-admin
environment:
DYNAMO_ENDPOINT: http://dynamodb:8000
depends_on:
- dynamodb
ports:
- 8080:8001
dynamodb:
image: amazon/dynamodb-local
command: -jar DynamoDBLocal.jar -sharedDb
ports:
- 8000:8000
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ jobs:
uses: actions/checkout@v2
- name: Test
env:
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: foo
AWS_SECRET_ACCESS_KEY: bar
ENDPOINT_URL: http://dynamodb:8000/
run: make test
run: make test
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ run:
--authorization=basic \
--set=services.opa.credentials=null

serve-docs:
# Requires nodejs and docsify
docsify serve ./docs

unit:
go test -v -short ./...

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OPA DynamoDB

Infinitely scalable policy store with instantaneous policy updates for use by small and enterprise scale teams wanting to use Open Policy Agent.
Scalable policy store with real-time policy updates for use by small and enterprise scale teams wanting to use Open Policy Agent.

OPA DynamoDB adds custom functionality to rego policies to query data from DynamoDB.

Expand All @@ -10,11 +10,13 @@ OPA has several strategies for managing policies at scale and accepting internal
- AWS credentials can be infered by the credentials chain in Goland AWS SDK
- Retry logic and caching are implemented by the AWS SDK and this implementation

See the [User Documentation](https://mneil.github.io/opa-dynamodb) for setup and usage.

## DynamoDB As A Backend

DynamoDB is an excellent backend for policy data. You can store documentesque data across dynamo rows and query them using a collections pattern. This method is efficient (single read to get entire policy) and scalable (dynamodb storage is extremely scalable).

If you want to understand more about Single Table Design, item collections, and DynamoDB in general I recommend this book by Alex Debrie https://www.dynamodbbook.com/. I have no affiliation with Alex or his book. It's that good.
If you want to understand more about Single Table Design, item collections, and DynamoDB in general I recommend this book by Alex Debrie https://www.dynamodbbook.com/. I have no affiliation with Alex or his book.

## Architecture

Expand Down
Empty file added docs/.nojekyll
Empty file.
15 changes: 15 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# OPA DynamoDB

Scalable policy store with real-time policy updates for use by small and enterprise scale teams wanting to use Open Policy Agent.

OPA DynamoDB adds custom functionality to rego policies to query data from DynamoDB.

OPA has several strategies for managing policies at scale and accepting internal data which you can [read about here](https://www.openpolicyagent.org/docs/latest/external-data/). This repository implements [Option 5](https://www.openpolicyagent.org/docs/latest/external-data/#option-5-pull-data-during-evaluation) using DynamoDB as the external data source. This implementation also removes the current limitations described by OPA.

- Using this runtime you can test your policies against external data
- AWS credentials can be infered by the credentials chain in Goland AWS SDK
- Retry logic and caching are implemented by the AWS SDK and this implementation

# Examples

Read the [Getting Started](quickstart.md) for examples
12 changes: 12 additions & 0 deletions docs/_coverpage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

# OPA DynamoDB <small>0.1.0</small>

> DynamoDB Backend for Open Policy Agent

[GitHub](https://github.com/mneil/opa-dynamodb/)
[Get Started](#opa-dynamodb)


<!-- background color -->

![color](linear-gradient(to-right,#C4B3FF,#DAFFB3))
7 changes: 7 additions & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- [Getting started](quickstart.md)

- Guide
- [Configuration](configuration.md)
- [Deploy](deploy.md)

- [Changelog](changelog.md)
11 changes: 11 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

## v0.1.0

Initial Release

Working dynamodb backend with assumptions:

- Must have both a hash key and range key on your primary partition
- Assumed PK and SK for the key names but can be overridden with environment variables
- Tests exist to prove functionality
10 changes: 10 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Configuration

OPA DynamoDB can be configured using environment variable. This table describes the default settings and the variable that you can set to change them.

| Variable | Default | Required | Description |
|--------------|-------------|----------|------------------------------------------------------|
| DYNAMO_TABLE | OpaDynamoDB | No | The name of the table to get data from |
| DYNAMO_PK | PK | No | The hash (partition) key of the primary partition |
| DYNAMO_SK | SK | No | The sort (range) key of the primary partition |
| ENDPOINT_URL | "" | No | DynamoDB API url. Useful for testing w/ dynamo local |
1 change: 1 addition & 0 deletions docs/deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Deploy
40 changes: 40 additions & 0 deletions docs/examples/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
version: '3'
services:
opa:
image: mneil/opa-dynamodb
command:
- run
- --server
- --log-format=json-pretty
- --set=decision_logs.console=true
- --log-level=debug
- /quickstart/rbac.rego
environment:
ENDPOINT_URL: http://dynamodb:8000/
# No need to change this. DynamoLocal doesn't verify authentication
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: foo
AWS_SECRET_ACCESS_KEY: bar
TABLE_NAME: OpaDynamoDB
ports:
- 8181:8181
depends_on:
- dynamodb
links:
- dynamodb:dynamodb
volumes:
- .:/quickstart
dynamodb-admin:
image: aaronshaf/dynamodb-admin
environment:
DYNAMO_ENDPOINT: http://dynamodb:8000
depends_on:
- dynamodb
ports:
- 8001:8001
dynamodb:
image: amazon/dynamodb-local
command: -jar DynamoDBLocal.jar -sharedDb
ports:
- 9000:8000

30 changes: 30 additions & 0 deletions docs/examples/rbac.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package rbac

# user-role assignments
# user_roles := dynamodb.policy("foo/bar", "alice")
# user_roles := {
# "alice": ["engineering", "webdev"],
# "bob": ["hr"]
# }

# role-permissions assignments
role_permissions := {
"engineering": [{"action": "read", "object": "server123"}],
"webdev": [{"action": "read", "object": "server123"},
{"action": "write", "object": "server123"}],
"hr": [{"action": "read", "object": "database456"}]
}
# lookup the list of roles for the user
policy := dynamodb.policy(input.namespace, input.principal)
# logic that implements RBAC.
default allow = false
allow {
# for each role in that list
r := policy.roles[_]
# lookup the permissions list for role r
permissions := role_permissions[r]
# for each permission
p := permissions[_]
# check if the permission granted to r matches the user's request
p == {"action": input.action, "object": input.object}
}
Binary file added docs/images/table-creation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
coverpage: true,
name: 'OPA Dynamo DB',
repo: 'https://github.com/mneil/opa-dynamodb',
loadSidebar: true,
}
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
</body>
</html>
69 changes: 69 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Quick Start

Prerequisites

- Docker w/ Docker Compose

The fastest way to get started is with Docker and Docker Compose. You can run OPA and Dynamo DB on your own machine. Create a new folder to copy files into.

Start by creating a new rego file for a policy. Create a file named `rbac.rego` with the following contents to create an RBAC policy:

[filename](examples/rbac.rego ':include :type=code')

Create a new file named `docker-compose.yml` and add the following contents:

[filename](examples/docker-compose.yml ':include :type=code')

Run the docker images with `docker-compose up -d`. This compose file will start the opa-dynamo server, a dynamodb local instance, and a [dynamodb-admin instance](https://github.com/aaronshaf/dynamodb-admin).

You should see the 3 services start. Now you can open a browser to [http://localhost:8001](http://localhost:8001) and use the DynamoDB Admin interface.

!> **WSL2 Users** may need to open on on the ipv6 address [http://[::1]:8001/](http://[::1]:8001/). See [this issue](https://github.com/microsoft/WSL/issues/4983) for more information

?> _DynamoDB Admin_ is a GUI for exporing Dynamo data locally. It's not necessary to use this but it makes this example easier.

Using the GUI, create a new table named `OpaDynamoDB`. This is the name of our table configured in the compose file. Set the Hash attribute to `PK` and the Range attribute to `SK`.

![Table Creation](images/table-creation.png)

Edit the table and add a policy to evaluate with our rego. Click create item and add two users with their roles:

?> You have to add items individually right now in the GUI. So create one user, then repeat the process to create the next user.

```json
{
"PK": "foo/bar",
"SK": "alice",
"roles": ["engineering", "webdev"]
}
```
```json
{
"PK": "foo/bar",
"SK": "bob",
"roles": ["hr"]
}
```

Up to this point you have:

- Defined a RBAC policy file
- Started a local DynamoDB Database
- Started OPA DynamoDB and connected to the database
- Filled the database with policy information

Now you can query OPA to evaluate your policy file. The following query will check if user bob has the ability to read data on server123.

```sh
curl -X POST http://localhost:8181/v1/data/rbac/allow \
-H "Content-Type: application/json" \
--data '{"input":{"namespace":"foo/bar","principal":"bob","action":"read","object":"server123"}}'
```

You should receive a response similar to:

```json
{"decision_id":"648eccb3-5d8b-4001-8a7a-9e87014ea36a","result":false}
```

Try changing the principal to alice instead and the result will be true.
62 changes: 30 additions & 32 deletions policy/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,51 +90,44 @@ func TestPolicyDataIntegration(t *testing.T) {
policy string
principal string
namespace string
data []map[string]string
action string
object string
allow bool
}{
{
name: "rbac not allow bob",
policy: "rbac/authz",
principal: "baz",
principal: "bob",
namespace: "foo/bar",
data: []map[string]string{
{
"user": "bob",
"action": "read",
"object": "server123",
},
},
allow: false,
action: "read",
object: "server123",
allow: false,
},
{
name: "rbac allow alice",
policy: "rbac/authz",
principal: "baz",
principal: "alice",
namespace: "foo/bar",
data: []map[string]string{
{
"user": "alice",
"action": "read",
"object": "server123",
},
},
allow: false,
action: "read",
object: "server123",
allow: true,
},
}
// our actual tests is here in the loop
for _, c := range cases {
body, err := json.Marshal(struct {
Input interface{}
Input interface{} `json:"input"`
}{
Input: struct {
principal string
namespace string
data []map[string]string
Principal string `json:"principal"`
Namespace string `json:"namespace"`
Action string `json:"action"`
Object string `json:"object"`
}{
principal: c.principal,
namespace: c.namespace,
data: c.data,
Principal: c.principal,
Namespace: c.namespace,
Action: c.action,
Object: c.object,
},
})
resp, err := http.Post(
Expand All @@ -146,13 +139,18 @@ func TestPolicyDataIntegration(t *testing.T) {
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode, c.name)
body, err = ioutil.ReadAll(resp.Body)
var v struct {
result struct {
allow bool
}
}
v := Response{}
json.Unmarshal(body, &v)
assert.Equal(t, c.allow, v.result.allow, c.name)
fmt.Print("THE RESULT")
fmt.Print(string(body))
assert.Equal(t, c.allow, v.Result.Allow, c.name)
}
}

type Result struct {
Allow bool `json:"allow"`
}

type Response struct {
Result Result `json:"result"`
}
Loading