diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ab125d1..a8ff356 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9780c07..86667d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 \ No newline at end of file + run: make test diff --git a/Makefile b/Makefile index 85e1157..cd74303 100644 --- a/Makefile +++ b/Makefile @@ -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 ./... diff --git a/README.md b/README.md index edc290a..1b764b0 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2cf4234 --- /dev/null +++ b/docs/README.md @@ -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 diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 0000000..872cd31 --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,12 @@ + +# OPA DynamoDB 0.1.0 + +> DynamoDB Backend for Open Policy Agent + +[GitHub](https://github.com/mneil/opa-dynamodb/) +[Get Started](#opa-dynamodb) + + + + +![color](linear-gradient(to-right,#C4B3FF,#DAFFB3)) diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 0000000..9125ac5 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,7 @@ +- [Getting started](quickstart.md) + +- Guide + - [Configuration](configuration.md) + - [Deploy](deploy.md) + +- [Changelog](changelog.md) diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..7740976 --- /dev/null +++ b/docs/changelog.md @@ -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 diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..d06a853 --- /dev/null +++ b/docs/configuration.md @@ -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 | diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..4a8d593 --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1 @@ +# Deploy \ No newline at end of file diff --git a/docs/examples/docker-compose.yml b/docs/examples/docker-compose.yml new file mode 100644 index 0000000..b34d1f4 --- /dev/null +++ b/docs/examples/docker-compose.yml @@ -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 + diff --git a/docs/examples/rbac.rego b/docs/examples/rbac.rego new file mode 100644 index 0000000..45f4b88 --- /dev/null +++ b/docs/examples/rbac.rego @@ -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} +} diff --git a/docs/images/table-creation.png b/docs/images/table-creation.png new file mode 100644 index 0000000..d92b2df Binary files /dev/null and b/docs/images/table-creation.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..09b23be --- /dev/null +++ b/docs/index.html @@ -0,0 +1,23 @@ + + + + + Document + + + + + + +
+ + + + diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..22b72da --- /dev/null +++ b/docs/quickstart.md @@ -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. diff --git a/policy/policy_test.go b/policy/policy_test.go index 560d903..b7c9713 100644 --- a/policy/policy_test.go +++ b/policy/policy_test.go @@ -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( @@ -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"` } diff --git a/store/dynamodb.go b/store/dynamodb.go index ef4d22b..cc198e3 100644 --- a/store/dynamodb.go +++ b/store/dynamodb.go @@ -63,6 +63,7 @@ func (dynamo *DynamoStore) Get(namespace string, principal string) (interface{}, KeyConditionExpression: aws.String("#PK = :pk AND #SK = :sk"), TableName: aws.String(dynamo.TableName), } + log.Debugf("Query input %v", input) result, err := dynamo.svc.Query(input) if err != nil { log.Error("Error querying data from dynamodb") @@ -73,13 +74,13 @@ func (dynamo *DynamoStore) Get(namespace string, principal string) (interface{}, } return "", err } + log.Debugf("Query result items %v", result.Items) itemLength := len(result.Items) if itemLength == 0 { log.Debug("No items returned from dynamodb") return "", nil } - log.Debugf("%d items returned from dynamodb", itemLength) - items := make([]map[string]interface{}, len(result.Items)) + items := make([]map[string]interface{}, itemLength) for index, item := range result.Items { var tmpItem map[string]interface{} dynamodbattribute.UnmarshalMap(item, &tmpItem) @@ -87,6 +88,7 @@ func (dynamo *DynamoStore) Get(namespace string, principal string) (interface{}, delete(tmpItem, dynamo.SortKey) items[index] = tmpItem } - log.Debugf("Policy from dynamodb %v", items) - return items, nil + log.Debugf("Policy from dynamodb %v", items[0]) + // There should only ever be one based on the query above + return items[0], nil } diff --git a/store/dynamodb_test.go b/store/dynamodb_test.go index 9f6fe4d..f6501d8 100644 --- a/store/dynamodb_test.go +++ b/store/dynamodb_test.go @@ -101,10 +101,8 @@ func TestGet(t *testing.T) { err: nil, namespace: "foo", principal: "bar", - expect: []map[string]interface{}{ - { - "foo": "bar", - }, + expect: map[string]interface{}{ + "foo": "bar", }, }, } diff --git a/templates/fargate.yml b/templates/fargate.yml new file mode 100644 index 0000000..6fb49ad --- /dev/null +++ b/templates/fargate.yml @@ -0,0 +1,315 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: | + Deploy OPA DynamoDB to aws Fargate Modified from + https://github.com/1Strategy/fargate-cloudformation-example +Parameters: + VPC: + Type: AWS::EC2::VPC::Id + SubnetA: + Type: AWS::EC2::Subnet::Id + SubnetB: + Type: AWS::EC2::Subnet::Id + Certificate: + Type: String + Default: arn:aws:acm:region:123456789012:certificate/00000000-0000-0000-0000-000000000000 + Image: + Type: String + Default: opa-dynamodb:latest + ServiceName: + Type: String + Default: OPADynamoDB + ContainerPort: + Type: Number + Default: 80 + LoadBalancerPort: + Type: Number + Default: 443 + HealthCheckPath: + Type: String + Default: /healthcheck + HostedZoneName: + Type: String + Subdomain: + Type: String + Default: opa + MinContainers: + Type: Number + Default: 2 + MaxContainers: + Type: Number + Default: 10 + AutoScalingTargetValue: + Type: Number + Default: 50 + +Resources: + Cluster: + Type: AWS::ECS::Cluster + Properties: + ClusterName: !Join ['', [!Ref ServiceName, Cluster]] + TaskDefinition: + Type: AWS::ECS::TaskDefinition + DependsOn: LogGroup + Properties: + Family: !Join ['', [!Ref ServiceName, TaskDefinition]] + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + Cpu: 1024 + Memory: 1GB + ExecutionRoleArn: !Ref ExecutionRole + TaskRoleArn: !Ref TaskRole + ContainerDefinitions: + - Name: !Ref ServiceName + Image: !Ref Image + PortMappings: + - ContainerPort: !Ref ContainerPort + LogConfiguration: + LogDriver: awslogs + Options: + awslogs-region: !Ref AWS::Region + awslogs-group: !Ref LogGroup + awslogs-stream-prefix: ecs + ExecutionRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref ServiceName, ExecutionRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy + TaskRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref ServiceName, TaskRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: sts:AssumeRole + TaskPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyName: OpaDynamoTaskPolicy + Roles: + - !Ref TaskRole + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - dynamodb:ConditionCheckItem + - dynamodb:DeleteItem + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:UpdateItem + Resource: + - '*' + AutoScalingRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Join ['', [!Ref ServiceName, AutoScalingRole]] + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole + ContainerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref ServiceName, ContainerSecurityGroup]] + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: !Ref ContainerPort + ToPort: !Ref ContainerPort + SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup + LoadBalancerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: !Join ['', [!Ref ServiceName, LoadBalancerSecurityGroup]] + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: !Ref LoadBalancerPort + ToPort: !Ref LoadBalancerPort + CidrIp: 0.0.0.0/0 + Service: + Type: AWS::ECS::Service + DependsOn: + - ListenerHTTPS + Properties: + ServiceName: !Ref ServiceName + Cluster: !Ref Cluster + TaskDefinition: !Ref TaskDefinition + DeploymentConfiguration: + MinimumHealthyPercent: 100 + MaximumPercent: 200 + DesiredCount: 2 + HealthCheckGracePeriodSeconds: 30 + LaunchType: FARGATE + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + Subnets: + - !Ref SubnetA + - !Ref SubnetB + SecurityGroups: + - !Ref ContainerSecurityGroup + LoadBalancers: + - ContainerName: !Ref ServiceName + ContainerPort: !Ref ContainerPort + TargetGroupArn: !Ref TargetGroup + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 10 + HealthCheckPath: !Ref HealthCheckPath + HealthCheckTimeoutSeconds: 5 + UnhealthyThresholdCount: 2 + HealthyThresholdCount: 2 + Name: !Join ['', [!Ref ServiceName, TargetGroup]] + Port: !Ref ContainerPort + Protocol: HTTP + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 + TargetType: ip + VpcId: !Ref VPC + ListenerHTTPS: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref TargetGroup + Type: forward + LoadBalancerArn: !Ref LoadBalancer + Port: !Ref LoadBalancerPort + Protocol: HTTPS + Certificates: + - CertificateArn: !Ref Certificate + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: 60 + Name: !Join ['', [!Ref ServiceName, LoadBalancer]] + Scheme: internet-facing + SecurityGroups: + - !Ref LoadBalancerSecurityGroup + Subnets: + - !Ref SubnetA + - !Ref SubnetB + DNSRecord: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneName: !Join ['', [!Ref HostedZoneName, .]] + Name: !Join ['', [!Ref Subdomain, ., !Ref HostedZoneName, .]] + Type: A + AliasTarget: + DNSName: !GetAtt LoadBalancer.DNSName + HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Join ['', [/ecs/, !Ref ServiceName, TaskDefinition]] + AutoScalingTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + Properties: + MinCapacity: !Ref MinContainers + MaxCapacity: !Ref MaxContainers + ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]] + ScalableDimension: ecs:service:DesiredCount + ServiceNamespace: ecs + RoleARN: !GetAtt AutoScalingRole.Arn + AutoScalingPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + Properties: + PolicyName: !Join ['', [!Ref ServiceName, AutoScalingPolicy]] + PolicyType: TargetTrackingScaling + ScalingTargetId: !Ref AutoScalingTarget + TargetTrackingScalingPolicyConfiguration: + PredefinedMetricSpecification: + PredefinedMetricType: ECSServiceAverageCPUUtilization + ScaleInCooldown: 10 + ScaleOutCooldown: 10 + TargetValue: !Ref AutoScalingTargetValue + DynamoTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: PK + AttributeType: S + - AttributeName: SK + AttributeType: S + BillingMode: PAY_PER_REQUEST + KeySchema: + - AttributeName: PK + KeyType: HASH + - AttributeName: SK + KeyType: RANGE + PointInTimeRecoverySpecification: + PointInTimeRecoveryEnabled: True + SSESpecification: + KMSMasterKeyId: String + SSEEnabled: True + SSEType: KMS + TableName: OpaDynamoDB + DynamoKey: + Type: AWS::KMS::Key + Properties: + Description: A KMS Key for OPA DynamoDB Table + Enabled: True + EnableKeyRotation: True + KeyPolicy: + Version: 2012-10-17 + Statement: + - Sid : Allow access through Amazon DynamoDB for all principals in the account that are authorized to use Amazon DynamoDB + Effect : Allow + Principal : + AWS : '*' + Action: + - kms:Encrypt + - kms:Decrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + - kms:CreateGrant + - kms:DescribeKey + Resource: '*' + Condition: + StringEquals: + kms:CallerAccount: !Ref AWS::AccountId + kms:ViaService: !Sub dynamodb.${AWS::Region}.amazonaws.com + - Sid: Allow direct access to key metadata to the account + Effect: Allow + Principal: + AWS : !Sub arn:aws:iam::${AWS::AccountId}:root + Action: + - kms:Describe* + - kms:Get* + - kms:List* + - kms:RevokeGrant + Resource : '*' + - Sid: Allow DynamoDB Service with service principal name dynamodb.amazonaws.com to describe the key directly + Effect: Allow + Principal: + Service: dynamodb.amazonaws.com + Action: + - kms:Describe* + - kms:Get* + - kms:List* + Resource : '*' + +Outputs: + Endpoint: + Description: Endpoint + Value: !Join ['', ['https://', !Ref DNSRecord]] \ No newline at end of file diff --git a/testdata/attestors/rbac/authz.rego b/testdata/attestors/rbac/authz.rego index 5ea91b4..553e8b3 100644 --- a/testdata/attestors/rbac/authz.rego +++ b/testdata/attestors/rbac/authz.rego @@ -19,13 +19,13 @@ role_permissions := { default allow = false allow { # lookup the list of roles for the user - roles := dynamodb.policy("foo/bar", input.user) + policy := dynamodb.policy(input.namespace, input.principal) # for each role in that list - r := roles[_] + 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} -} \ No newline at end of file +}