Skip to content

Commit

Permalink
Merge pull request #12 from StyraInc/dynamic-policy-composition
Browse files Browse the repository at this point in the history
Dynamic policy composition
  • Loading branch information
anderseknert committed Mar 24, 2022
2 parents b449717 + 02340b9 commit aa10d86
Show file tree
Hide file tree
Showing 21 changed files with 438 additions and 299 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/opa-tests.yaml
@@ -1,4 +1,4 @@
name: Test OPA Templates
name: Test OPA Templates
on: [push]
jobs:
Test-OPA-Templates:
Expand All @@ -13,7 +13,7 @@ jobs:
- name: Setup OPA
uses: open-policy-agent/setup-opa@v1
with:
version: ${{ matrix.version }}
version: ${{ matrix.version }}

- name: Test OPA Templates
run: opa test templates/*/opa/*.rego -v
run: opa test -v policy/
20 changes: 20 additions & 0 deletions policy/authz.rego
@@ -0,0 +1,20 @@
# METADATA
# description: |
# Optional authorization policy to use for protecting the OPA REST API if
# exposed on a public endpoint.
# related_resources:
# - description: OPA documentation on authentication and authorization
# ref: https://www.openpolicyagent.org/docs/latest/security/#authentication-and-authorization
#
package system.authz

default allow = false

# METADATA
# description: |
# See the README.md file contained in this repo for how to configure an AWS Secret to
# use as a token for client connections.
#
allow {
input.identity == "changeme"
}
5 changes: 5 additions & 0 deletions policy/aws.rego
@@ -0,0 +1,5 @@
# METADATA
# scope: subpackages
# organizations:
# - Styra
package aws
15 changes: 15 additions & 0 deletions policy/ec2/security_group/security_group.rego
@@ -0,0 +1,15 @@
package aws.ec2.securitygroup

import future.keywords

deny[msg] {
input.resource.properties.SecurityGroupIngress[0].CidrIp == "0.0.0.0/0"

msg := sprintf("Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s", [input.resource.id])
}

deny[msg] {
input.resource.properties.SecurityGroupIngress[0].CidrIpv6 == "::/0"

msg := sprintf("Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s", [input.resource.id])
}
@@ -1,45 +1,35 @@
package aws.sg.open_ingress.tests
package aws.ec2.securitygroup_test

import future.keywords

import data.aws.sg.open_ingress.deny

mock_create := {
"action": "CREATE",
"hook": "StyraOPA::SecurityGroup::Hook",
"resource": {
"id": "SecurityGroup",
"name": "AWS::EC2::SecurityGroup",
"properties": {},
"type": "AWS::EC2::SecurityGroup"
}
}
import data.aws.ec2.securitygroup.deny

with_properties(obj) = {"resource": {"properties": obj}}
import data.test_helpers.assert_empty
import data.test_helpers.create_with_properties

test_deny_if_security_group_allows_all_destinations {
inp := object.union(mock_create, with_properties({
inp := create_with_properties("AWS::EC2::SecurityGroup", "SecurityGroup", {
"SecurityGroupIngress": [
{
"CidrIp": "0.0.0.0/0",
"IpProtocol": "-1"
}
]
}))
})

deny["Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): SecurityGroup"] with input as inp
}

test_allow_if_security_group_cidr_is_set {
inp := object.union(mock_create, with_properties({
inp := create_with_properties("AWS::EC2::SecurityGroup", "SecurityGroup", {
"SecurityGroupIngress": [
{
"CidrIp": "10.0.0.0/16",
"IpProtocol": "-1"
}
]
}))
})

count(deny) == 0 with input as inp
assert_empty(deny) with input as inp
}

@@ -1,46 +1,34 @@
package aws.iam
package aws.iam.role

import future.keywords

excludedPrincipalPrefixes := ["excluded", "iam-excluded", "test-excluded-user"]

deny[msg] {
input.action in {"CREATE", "UPDATE"}
iam_resource_type
not excluded_principal_name
not permission_boundary_exists

msg := sprintf("PermissionsBoundary is not set for %s", [input.resource.id])
}
excluded_principal_name {
name := input.resource.properties.UserName
some prefix in excludedPrincipalPrefixes
startswith(name, prefix)
}
excluded_principal_name {
name := input.resource.properties.RoleName
some prefix in excludedPrincipalPrefixes
startswith(name, prefix)
}
permission_boundary_exists {
input.resource.properties.PermissionsBoundary
}

deny[msg] {
input.action in {"CREATE", "UPDATE"}
iam_resource_type
not excluded_principal_name
permission_boundary_exists
not valid_permission_boundary

msg := sprintf("PermissionsBoundary %s is not allowed for %s", [input.resource.properties.PermissionsBoundary, input.resource.id])
}
iam_resource_type {
input.resource.type == "AWS::IAM::Role"

excluded_principal_name {
name := input.resource.properties.RoleName
some prefix in excludedPrincipalPrefixes
startswith(name, prefix)
}
iam_resource_type {
input.resource.type == "AWS::IAM::User"

permission_boundary_exists {
input.resource.properties.PermissionsBoundary
}

valid_permission_boundary {
input.resource.properties.PermissionsBoundary == "arn:aws:iam::555555555555:policy/s3_deny_permissions_boundary"
}
@@ -1,44 +1,37 @@
package aws.iam.tests
package aws.iam.role_test

import future.keywords

import data.aws.iam.deny

mock_create := {
"action": "CREATE",
"hook": "Styra::OPA::Hook",
"resource": {
"id": "IAMRoleTest",
"name": "AWS::IAM::Role",
"type": "AWS::IAM::Role",
"properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
}}]}
}
import data.aws.iam.role.deny

import data.test_helpers.assert_empty
import data.test_helpers.create_with_properties
import data.test_helpers.with_properties

mock_create := create_with_properties("AWS::IAM::Role", "IAMRoleTest", {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
}
}]
}
}

with_properties(obj) = {"resource": {"properties": obj}}
})

test_deny_auto_generated_name_not_excluded {
inp := object.union(mock_create, with_properties({
"RoleName": "iam-not-excluded-cfn-hooks-cfn-stack-1-fail-046693375555",
"PermissionsBoundary": "arn:aws:iam::555555555555:policy/invalid_s3_deny_permissions_boundary"
}))

deny["PermissionsBoundary arn:aws:iam::555555555555:policy/invalid_s3_deny_permissions_boundary is not allowed for IAMRoleTest"] with input as inp
}

test_deny_permission_boundary_not_set {
inp := mock_create

deny["PermissionsBoundary is not set for IAMRoleTest"] with input as inp
deny["PermissionsBoundary is not set for IAMRoleTest"] with input as mock_create
}

test_allow_permission_boundary_included {
Expand All @@ -47,20 +40,12 @@ test_allow_permission_boundary_included {
"PermissionsBoundary": "arn:aws:iam::555555555555:policy/s3_deny_permissions_boundary"
}))

count(deny) == 0 with input as inp
assert_empty(deny) with input as inp
}
test_allow_role_name_excluded {
inp := object.union(mock_create, with_properties({
"RoleName": "excluded-cfn-hooks-stack1-046693375555"
}))

count(deny) == 0 with input as inp
assert_empty(deny) with input as inp
}

test_allow_user_name_excluded {
inp := object.union(mock_create, with_properties({
"UserName": "excluded-cfn-hooks-stack1-046693375555"
}))

count(deny) == 0 with input as inp
}
34 changes: 34 additions & 0 deletions policy/iam/user/user.rego
@@ -0,0 +1,34 @@
package aws.iam.user

import future.keywords

excludedPrincipalPrefixes := ["excluded", "iam-excluded", "test-excluded-user"]

deny[msg] {
not excluded_principal_name
not permission_boundary_exists

msg := sprintf("PermissionsBoundary is not set for %s", [input.resource.id])
}

deny[msg] {
not excluded_principal_name
permission_boundary_exists
not valid_permission_boundary

msg := sprintf("PermissionsBoundary %s is not allowed for %s", [input.resource.properties.PermissionsBoundary, input.resource.id])
}

excluded_principal_name {
name := input.resource.properties.UserName
some prefix in excludedPrincipalPrefixes
startswith(name, prefix)
}

permission_boundary_exists {
input.resource.properties.PermissionsBoundary
}

valid_permission_boundary {
input.resource.properties.PermissionsBoundary == "arn:aws:iam::555555555555:policy/s3_deny_permissions_boundary"
}
54 changes: 54 additions & 0 deletions policy/iam/user/user_test.rego
@@ -0,0 +1,54 @@
package aws.iam.user_test

import future.keywords

import data.aws.iam.user.deny

import data.test_helpers.assert_empty
import data.test_helpers.create_with_properties
import data.test_helpers.with_properties

mock_create := create_with_properties("AWS::IAM::User", "IAMUserTest", {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
}
}]
}
})

test_deny_auto_generated_name_not_excluded {
inp := object.union(mock_create, with_properties({
"RoleName": "iam-not-excluded-cfn-hooks-cfn-stack-1-fail-046693375555",
"PermissionsBoundary": "arn:aws:iam::555555555555:policy/invalid_s3_deny_permissions_boundary"
}))

deny["PermissionsBoundary arn:aws:iam::555555555555:policy/invalid_s3_deny_permissions_boundary is not allowed for IAMUserTest"] with input as inp
}

test_deny_permission_boundary_not_set {
inp := mock_create

deny["PermissionsBoundary is not set for IAMUserTest"] with input as inp
}

test_allow_permission_boundary_included {
inp := object.union(mock_create, with_properties({
"RoleName": "cfn-hooks-pass-046693375555",
"PermissionsBoundary": "arn:aws:iam::555555555555:policy/s3_deny_permissions_boundary"
}))

assert_empty(deny) with input as inp
}

test_allow_user_name_excluded {
inp := object.union(mock_create, with_properties({
"UserName": "excluded-cfn-hooks-stack1-046693375555"
}))

assert_empty(deny) with input as inp
}

0 comments on commit aa10d86

Please sign in to comment.