# PolicyUniverse
[![Version](http://img.shields.io/pypi/v/policyuniverse.svg?style=flat)](https://pypi.python.org/pypi/policyuniverse/)
[![Build Status](https://github.com/Netflix-Skunkworks/policyuniverse/workflows/Python%20package/badge.svg)](https://github.com/Netflix-Skunkworks/policyuniverse/actions)
[![Updater Status](https://github.com/Netflix-Skunkworks/policyuniverse/actions/workflows/updater.yml/badge.svg)](https://github.com/Netflix-Skunkworks/policyuniverse/actions/workflows/updater.yml)
[![Coverage Status](https://coveralls.io/repos/github/Netflix-Skunkworks/policyuniverse/badge.svg?branch=master&1)](https://coveralls.io/github/Netflix-Skunkworks/policyuniverse?branch=master)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
This package provides classes to parse AWS IAM and Resource Policies.
Additionally, this package can expand wildcards in AWS Policies using permissions obtained from the AWS Policy Generator.
See the [Service and Permissions data](policyuniverse/data.json).
_This package can also minify an AWS policy to help you stay under policy size limits. Avoid doing this if possible, as it creates ugly policies._ 💩
# Install:
`pip install policyuniverse`
# Usage:
- [ARN class](#reading-arns)
- [Policy class](#iam-and-resource-policies)
- [Statement class](#statements)
- [Action Categories](#action-categories)
- [Expanding and Minification](#expanding-and-minification)
## Reading ARNs
```python
from policyuniverse.arn import ARN
arn = ARN('arn:aws:iam::012345678910:role/SomeTestRoleForTesting')
assert arn.error == False
assert arn.tech == 'iam'
assert arn.region == '' # IAM is universal/global
assert arn.account_number == '012345678910'
assert arn.name == 'role/SomeTestRoleForTesting'
assert arn.partition == 'aws'
assert arn.root == False # Not the root ARN
assert arn.service == False # Not an AWS service like lambda.amazonaws.com
arn = ARN('012345678910')
assert arn.account_number == '012345678910'
arn = ARN('lambda.amazonaws.com')
assert arn.service == True
assert arn.tech == 'lambda'
```
## IAM and Resource Policies
### Policy with multiple statements
```python
# Two statements, both with conditions
policy05 = dict(
Version='2010-08-14',
Statement=[
dict(
Effect='Allow',
Principal='arn:aws:iam::012345678910:root',
Action=['s3:*'],
Resource='*',
Condition={
'IpAddress': {
'AWS:SourceIP': ['0.0.0.0/0']
}}),
dict(
Effect='Allow',
Principal='arn:aws:iam::*:role/Hello',
Action=['ec2:*'],
Resource='*',
Condition={
'StringLike': {
'AWS:SourceOwner': '012345678910'
}})
])
from policyuniverse.policy import Policy
from policyuniverse.statement import ConditionTuple, PrincipalTuple
policy = Policy(policy05)
assert policy.whos_allowed() == set([
PrincipalTuple(category='principal', value='arn:aws:iam::*:role/Hello'),
PrincipalTuple(category='principal', value='arn:aws:iam::012345678910:root'),
ConditionTuple(category='cidr', value='0.0.0.0/0'),
ConditionTuple(category='account', value='012345678910')
])
# The given policy is not internet accessible.
# The first statement is limited by the principal, and the condition is basically a no-op.
# The second statement has a wildcard principal, but uses the condition to lock it down.
assert policy.is_internet_accessible() == False
```
### Internet Accessible Policy:
```python
# An internet accessible policy:
policy01 = dict(
Version='2012-10-08',
Statement=dict(
Effect='Allow',
Principal='*',
Action=['rds:*'],
Resource='*',
Condition={
'IpAddress': {
'AWS:SourceIP': ['0.0.0.0/0']
}
}))
policy = Policy(policy01)
assert policy.is_internet_accessible() == True
assert policy.internet_accessible_actions() == set(['rds:*'])
```
## Statements
A policy is simply a collection of statements.
```python
statement12 = dict(
Effect='Allow',
Principal='*',
Action=['rds:*'],
Resource='*',
Condition={
'StringEquals': {
'AWS:SourceVPC': 'vpc-111111',
'AWS:Sourcevpce': 'vpce-111111',
'AWS:SourceOwner': '012345678910',
'AWS:SourceAccount': '012345678910'
},
'StringLike': {
'AWS:userid': 'AROAI1111111111111111:*'
},
'ARNLike': {
'AWS:SourceArn': 'arn:aws:iam::012345678910:role/Admin'
},
'IpAddressIfExists': {
'AWS:SourceIP': [
'123.45.67.89',
'10.0.7.0/24',
'172.16.0.0/16']
}
})
from policyuniverse.statement import Statement
from policyuniverse.statement import ConditionTuple, PrincipalTuple
statement = Statement(statement12)
assert statement.effect == 'Allow'
assert statement.actions == set(['rds:*'])
# rds:* expands out to ~88 individual permissions
assert len(statement.actions_expanded) == 88
assert statement.uses_not_principal() == False
assert statement.principals == set(['*'])
assert statement.condition_arns == set(['arn:aws:iam::012345678910:role/Admin'])
assert statement.condition_accounts == set(['012345678910'])
assert statement.condition_userids == set(['AROAI1111111111111111:*'])
assert statement.condition_cidrs == set(['10.0.7.0/24', '172.16.0.0/16', '123.45.67.89'])
assert statement.condition_vpcs == set(['vpc-111111'])
assert statement.condition_vpces == set(['vpce-111111'])
assert statement.is_internet_accessible() == False
assert statement.whos_allowed() == set([
PrincipalTuple(category='principal', value='*'),
ConditionTuple(category='cidr', value='123.45.67.89'),
ConditionTuple(category='account', value='012345678910'),
ConditionTuple(category='userid', value='AROAI1111111111111111:*'),
ConditionTuple(category='vpc', value='vpc-111111'),
ConditionTuple(category='arn', value='arn:aws:iam::012345678910:role/Admin'),
ConditionTuple(category='cidr', value='172.16.0.0/16'),
ConditionTuple(category='vpce', value='vpce-111111'),
ConditionTuple(category='cidr', value='10.0.7.0/24')])
```
## Action Categories
```python
policy = {
"Statement": [{
"Action": ["s3:put*", "sqs:get*", "sns:*"],
"Resource": "*",
"Effect": "Allow"
}]
}
from policyuniverse.policy import Policy
p = Policy(policy)
for k, v in p.action_summary().items():
print(k,v)
>>> ('s3', set([u'Write', u'Permissions', u'Tagging']))
>>> ('sqs', set([u'List']))
>>> ('sns', set([u'List', u'Read', u'Write', u'Permissions']))
```
Possible categories are `Permissions`, `Write`, `Read`, `Tagging`, and `List`. This data can be used to summarize statements and policies and to look for sensitive permissions.
## Expanding and Minification
```python
from policyuniverse.expander_minimizer import expand_policy
from policyuniverse.expander_minimizer import minimize_policy
policy = {
"Statement": [{
"Action": ["swf:res*"],
"Resource": "*",
"Effect": "Allow"
}]
}
expanded_policy = expand_policy(policy=policy)
>>> Start size: 131. End size: 286
print(expanded_policy == {
"Statement": [{
"Action": [
"swf:respondactivitytaskcanceled",
"swf:respondactivitytaskcompleted",
"swf:respondactivitytaskfailed",
"swf:responddecisiontaskcompleted"
],
"Resource": "*",
"Effect": "Allow"
}]
})
>>> True
minimized_policy = minimize_policy(policy=expanded_policy, minchars=3)
>>> Skipping prefix r because length of 1
>>> Skipping prefix re because length of 2
>>> Skipping prefix r because length of 1
>>> Skipping prefix re because length of 2
>>> Skipping prefix r because length of 1
>>> Skipping prefix re because length of 2
>>> Skipping prefix r because length of 1
>>> Skipping prefix re because length of 2
>>> Start size: 286. End size: 131
print(minimized_policy == policy)
>>> True
```
Raw data
{
"_id": null,
"home_page": "https://github.com/Netflix-Skunkworks/policyuniverse",
"name": "policyuniverse",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": "",
"keywords": "iam,arn,action_groups,condition,policy,statement,wildcard",
"author": "Patrick Kelley",
"author_email": "patrickbarrettkelley@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/03/a2/6cf14186b746fbcab73e507968e0b1927ad2e91dcb67af967f65d6cbe6c1/policyuniverse-1.5.1.20231109.tar.gz",
"platform": null,
"description": "# PolicyUniverse\n\n[![Version](http://img.shields.io/pypi/v/policyuniverse.svg?style=flat)](https://pypi.python.org/pypi/policyuniverse/)\n\n[![Build Status](https://github.com/Netflix-Skunkworks/policyuniverse/workflows/Python%20package/badge.svg)](https://github.com/Netflix-Skunkworks/policyuniverse/actions)\n\n[![Updater Status](https://github.com/Netflix-Skunkworks/policyuniverse/actions/workflows/updater.yml/badge.svg)](https://github.com/Netflix-Skunkworks/policyuniverse/actions/workflows/updater.yml)\n\n[![Coverage Status](https://coveralls.io/repos/github/Netflix-Skunkworks/policyuniverse/badge.svg?branch=master&1)](https://coveralls.io/github/Netflix-Skunkworks/policyuniverse?branch=master)\n\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)\n\nThis package provides classes to parse AWS IAM and Resource Policies.\n\nAdditionally, this package can expand wildcards in AWS Policies using permissions obtained from the AWS Policy Generator.\n\nSee the [Service and Permissions data](policyuniverse/data.json).\n\n_This package can also minify an AWS policy to help you stay under policy size limits. Avoid doing this if possible, as it creates ugly policies._ \ud83d\udca9\n\n# Install:\n\n`pip install policyuniverse`\n\n# Usage:\n\n- [ARN class](#reading-arns)\n- [Policy class](#iam-and-resource-policies)\n- [Statement class](#statements)\n- [Action Categories](#action-categories)\n- [Expanding and Minification](#expanding-and-minification)\n\n## Reading ARNs\n\n```python\nfrom policyuniverse.arn import ARN\narn = ARN('arn:aws:iam::012345678910:role/SomeTestRoleForTesting')\nassert arn.error == False\nassert arn.tech == 'iam'\nassert arn.region == '' # IAM is universal/global\nassert arn.account_number == '012345678910'\nassert arn.name == 'role/SomeTestRoleForTesting'\nassert arn.partition == 'aws'\nassert arn.root == False # Not the root ARN\nassert arn.service == False # Not an AWS service like lambda.amazonaws.com\n\narn = ARN('012345678910')\nassert arn.account_number == '012345678910'\n\narn = ARN('lambda.amazonaws.com')\nassert arn.service == True\nassert arn.tech == 'lambda'\n```\n\n## IAM and Resource Policies\n\n### Policy with multiple statements\n```python\n# Two statements, both with conditions\npolicy05 = dict(\n Version='2010-08-14',\n Statement=[\n dict(\n Effect='Allow',\n Principal='arn:aws:iam::012345678910:root',\n Action=['s3:*'],\n Resource='*',\n Condition={\n 'IpAddress': {\n 'AWS:SourceIP': ['0.0.0.0/0']\n }}),\n dict(\n Effect='Allow',\n Principal='arn:aws:iam::*:role/Hello',\n Action=['ec2:*'],\n Resource='*',\n Condition={\n 'StringLike': {\n 'AWS:SourceOwner': '012345678910'\n }})\n ])\n\nfrom policyuniverse.policy import Policy\nfrom policyuniverse.statement import ConditionTuple, PrincipalTuple\n\npolicy = Policy(policy05)\nassert policy.whos_allowed() == set([\n PrincipalTuple(category='principal', value='arn:aws:iam::*:role/Hello'),\n PrincipalTuple(category='principal', value='arn:aws:iam::012345678910:root'),\n ConditionTuple(category='cidr', value='0.0.0.0/0'),\n ConditionTuple(category='account', value='012345678910')\n])\n\n# The given policy is not internet accessible.\n# The first statement is limited by the principal, and the condition is basically a no-op.\n# The second statement has a wildcard principal, but uses the condition to lock it down.\nassert policy.is_internet_accessible() == False\n```\n\n### Internet Accessible Policy:\n\n```python\n# An internet accessible policy:\npolicy01 = dict(\n Version='2012-10-08',\n Statement=dict(\n Effect='Allow',\n Principal='*',\n Action=['rds:*'],\n Resource='*',\n Condition={\n 'IpAddress': {\n 'AWS:SourceIP': ['0.0.0.0/0']\n }\n }))\n\npolicy = Policy(policy01)\nassert policy.is_internet_accessible() == True\nassert policy.internet_accessible_actions() == set(['rds:*'])\n```\n\n## Statements\n\nA policy is simply a collection of statements.\n\n```python\nstatement12 = dict(\n Effect='Allow',\n Principal='*',\n Action=['rds:*'],\n Resource='*',\n Condition={\n 'StringEquals': {\n 'AWS:SourceVPC': 'vpc-111111',\n 'AWS:Sourcevpce': 'vpce-111111',\n 'AWS:SourceOwner': '012345678910',\n 'AWS:SourceAccount': '012345678910'\n },\n 'StringLike': {\n 'AWS:userid': 'AROAI1111111111111111:*'\n },\n 'ARNLike': {\n 'AWS:SourceArn': 'arn:aws:iam::012345678910:role/Admin'\n },\n 'IpAddressIfExists': {\n 'AWS:SourceIP': [\n '123.45.67.89',\n '10.0.7.0/24',\n '172.16.0.0/16']\n }\n })\n\nfrom policyuniverse.statement import Statement\nfrom policyuniverse.statement import ConditionTuple, PrincipalTuple\n\nstatement = Statement(statement12)\nassert statement.effect == 'Allow'\nassert statement.actions == set(['rds:*'])\n\n# rds:* expands out to ~88 individual permissions\nassert len(statement.actions_expanded) == 88\n\nassert statement.uses_not_principal() == False\nassert statement.principals == set(['*'])\nassert statement.condition_arns == set(['arn:aws:iam::012345678910:role/Admin'])\nassert statement.condition_accounts == set(['012345678910'])\nassert statement.condition_userids == set(['AROAI1111111111111111:*'])\nassert statement.condition_cidrs == set(['10.0.7.0/24', '172.16.0.0/16', '123.45.67.89'])\nassert statement.condition_vpcs == set(['vpc-111111'])\nassert statement.condition_vpces == set(['vpce-111111'])\nassert statement.is_internet_accessible() == False\nassert statement.whos_allowed() == set([\n PrincipalTuple(category='principal', value='*'),\n ConditionTuple(category='cidr', value='123.45.67.89'),\n ConditionTuple(category='account', value='012345678910'),\n ConditionTuple(category='userid', value='AROAI1111111111111111:*'),\n ConditionTuple(category='vpc', value='vpc-111111'),\n ConditionTuple(category='arn', value='arn:aws:iam::012345678910:role/Admin'),\n ConditionTuple(category='cidr', value='172.16.0.0/16'),\n ConditionTuple(category='vpce', value='vpce-111111'),\n ConditionTuple(category='cidr', value='10.0.7.0/24')])\n\n```\n\n\n## Action Categories\n```python\npolicy = {\n \"Statement\": [{\n \"Action\": [\"s3:put*\", \"sqs:get*\", \"sns:*\"],\n \"Resource\": \"*\",\n \"Effect\": \"Allow\"\n }]\n }\n\nfrom policyuniverse.policy import Policy\np = Policy(policy)\nfor k, v in p.action_summary().items():\n print(k,v)\n>>> ('s3', set([u'Write', u'Permissions', u'Tagging']))\n>>> ('sqs', set([u'List']))\n>>> ('sns', set([u'List', u'Read', u'Write', u'Permissions']))\n```\nPossible categories are `Permissions`, `Write`, `Read`, `Tagging`, and `List`. This data can be used to summarize statements and policies and to look for sensitive permissions.\n\n## Expanding and Minification\n```python\nfrom policyuniverse.expander_minimizer import expand_policy\nfrom policyuniverse.expander_minimizer import minimize_policy\n\npolicy = {\n \"Statement\": [{\n \"Action\": [\"swf:res*\"],\n \"Resource\": \"*\",\n \"Effect\": \"Allow\"\n }]\n }\n\nexpanded_policy = expand_policy(policy=policy)\n>>> Start size: 131. End size: 286\nprint(expanded_policy == {\n \"Statement\": [{\n \"Action\": [\n \"swf:respondactivitytaskcanceled\",\n \"swf:respondactivitytaskcompleted\",\n \"swf:respondactivitytaskfailed\",\n \"swf:responddecisiontaskcompleted\"\n ],\n \"Resource\": \"*\",\n \"Effect\": \"Allow\"\n }]\n })\n>>> True\n\nminimized_policy = minimize_policy(policy=expanded_policy, minchars=3)\n>>> Skipping prefix r because length of 1\n>>> Skipping prefix re because length of 2\n>>> Skipping prefix r because length of 1\n>>> Skipping prefix re because length of 2\n>>> Skipping prefix r because length of 1\n>>> Skipping prefix re because length of 2\n>>> Skipping prefix r because length of 1\n>>> Skipping prefix re because length of 2\n>>> Start size: 286. End size: 131\n\nprint(minimized_policy == policy)\n>>> True\n```\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Parse and Process AWS IAM Policies, Statements, ARNs, and wildcards.",
"version": "1.5.1.20231109",
"project_urls": {
"Homepage": "https://github.com/Netflix-Skunkworks/policyuniverse"
},
"split_keywords": [
"iam",
"arn",
"action_groups",
"condition",
"policy",
"statement",
"wildcard"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "41f565b66420c275e9b26513fdd6d84687403d11ac8be4650b67d1e5572b8f48",
"md5": "6d5f2ed284a1fe71b11cfffefed7b330",
"sha256": "0b0ece0ee8285af31fc39ce09c82a551ca62e62bc2842e23952503bccb973321"
},
"downloads": -1,
"filename": "policyuniverse-1.5.1.20231109-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "6d5f2ed284a1fe71b11cfffefed7b330",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=3.7",
"size": 484251,
"upload_time": "2023-11-30T19:12:43",
"upload_time_iso_8601": "2023-11-30T19:12:43.463346Z",
"url": "https://files.pythonhosted.org/packages/41/f5/65b66420c275e9b26513fdd6d84687403d11ac8be4650b67d1e5572b8f48/policyuniverse-1.5.1.20231109-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "03a26cf14186b746fbcab73e507968e0b1927ad2e91dcb67af967f65d6cbe6c1",
"md5": "b0b3c4b45e9d90190c42dd5b02cbe5d0",
"sha256": "74e56d410560915c2c5132e361b0130e4bffe312a2f45230eac50d7c094bc40a"
},
"downloads": -1,
"filename": "policyuniverse-1.5.1.20231109.tar.gz",
"has_sig": false,
"md5_digest": "b0b3c4b45e9d90190c42dd5b02cbe5d0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 469645,
"upload_time": "2023-11-30T19:12:46",
"upload_time_iso_8601": "2023-11-30T19:12:46.297481Z",
"url": "https://files.pythonhosted.org/packages/03/a2/6cf14186b746fbcab73e507968e0b1927ad2e91dcb67af967f65d6cbe6c1/policyuniverse-1.5.1.20231109.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-30 19:12:46",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Netflix-Skunkworks",
"github_project": "policyuniverse",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "policyuniverse"
}