GithubHelp home page GithubHelp logo

parliament's Introduction

parliament is an AWS IAM linting library. It reviews policies looking for problems such as:

  • malformed json
  • missing required elements
  • incorrect prefix and action names
  • incorrect resources or conditions for the actions provided
  • type mismatches
  • bad policy patterns

This library duplicates (and adds to!) much of the functionality in the web console page when reviewing IAM policies in the browser. We wanted that functionality as a library.

demo

Installation

pip install parliament

Usage

cat > test.json << EOF
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action":["s3:GetObject"],
        "Resource": ["arn:aws:s3:::bucket1"]
    }
}
EOF

parliament --file test.json

This will output:

MEDIUM - No resources match for the given action -  - [{'action': 's3:GetObject', 'required_format': 'arn:*:s3:::*/*'}] - {'actions': ['s3:GetObject'], 'filepath': 'test.json'}

This example is showing that the action s3:GetObject requires a resource matching an object path (ie. it must have a "/" in it).

The different input types allowed include:

  • --file: Filename
  • --directory: A directory path, for exmaple: --directory . --include_policy_extension json --exclude_pattern ".*venv.*"
  • --aws-managed-policies: For use specifically with the repo https://github.com/z0ph/aws_managed_policies
  • --auth-details-file: For use with the file returned by "aws iam get-account-authorization-details"
  • --string: Provide a string such as '{"Version": "2012-10-17","Statement": {"Effect": "Allow","Action": ["s3:GetObject", "s3:PutBucketPolicy"],"Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2/*"]}}'

Using parliament as a library

Parliament was meant to be used a library in other projects. A basic example follows.

from parliament import analyze_policy_string

analyzed_policy = analyze_policy_string(policy_doc)
for f in analyzed_policy.findings:
  print(f)

Custom config file

You may decide you want to change the severity of a finding, the text associated with it, or that you want to ignore certain types of findings. To support this, you can provide an override config file. First, create a test.json file:

{
    "Version": "2012-10-17",
    "Id": "123",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": "s3:abc",
        "Resource": "*"
      },
      {
        "Effect": "Allow",
        "Action": ["s3:*", "ec2:*"],
        "Resource": "arn:aws:s3:::test/*"
      }
    ]
 }

This will have two findings:

  • LOW - Unknown action - - Unknown action s3:abc
  • MEDIUM - No resources match for the given action

The second finding will be very long, because every s3 and ec2 action are expanded and most are incorrect for the S3 object path resource that is provided.

Now create a file config_override.yaml with the following contents:

UNKNOWN_ACTION:
  severity: MEDIUM
  ignore_locations:
  - filepath:
    - testa.json
    - .*.py

RESOURCE_MISMATCH:
  ignore_locations:
  - actions: ".*s3.*"

Now run: parliament --file test.json --config config_override.yaml You will have only one output: MEDIUM - Unknown action - - Unknown action s3:abc

Notice that the severity of that finding has been changed from a LOW to a MEDIUM. Also, note that the other finding is gone, because the previous RESOURCE_MISMATCH finding contained an actions element of ["s3:*", "ec2:*"]. The ignore logic converts the value you provide, and the finding value to lowercase, and then uses your string as a regex. This means that we are checking if s3 is in str(["s3:*", "ec2:*"])

Now rename test.json to testa.json and rerun the command. You will no longer have any output, because we are filtering based on the filepath for UNKNOWN_ACTION and filtering for any filepaths that contain testa.json or .py.

You can also check the exit status with echo $? and see the exit status is 0 when there are no findings. The exit status will be non-zero when there are findings.

You can have multiple elements in ignore_locations. For example,

- filepath: "test.json"
  action: "s3:GetObject"
  resource: 
  - "a"
  - "b"
- resource: "c.*"

Assuming the finding has these types of values in the location element, this will ignore any finding that matches the filepath to "test.json" AND action to "s3:GetObject" AND the resource to "a" OR "b". It will also ignore a resource that matches "c.*".

Custom auditors

Private Auditors

This section will show how to create your own private auditor to look for any policies that grant access to either of the sensitive buckets secretbucket and othersecretbucket.

Create a file test.json with contents:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::secretbucket/*"
    }
}

This is an example of the policy we want to alert on. That policy will normally not generate any findings.

Create the file config_override.yaml with contents:

SENSITIVE_BUCKET_ACCESS:
  title: Sensitive bucket access
  description: Allows read access to an important S3 bucket
  severity: MEDIUM
  group: CUSTOM

In the parliament directory (that contains iam_definition.json), create the directory private_auditors and the file parliament/private_auditors/sensitive_bucket_access.py

from parliament import is_arn_match, expand_action

def audit(policy):
    action_resources = {}
    for action in expand_action("s3:*"):
        # Iterates through a list of containing elements such as
        # {'service': 's3', 'action': 'GetObject'}
        action_name = "{}:{}".format(action["service"], action["action"])
        action_resources[action_name] = policy.get_allowed_resources(action["service"], action["action"])
    
    for action_name in action_resources:
        resources = action_resources[action_name]
        for r in resources:
            if is_arn_match("object", "arn:aws:s3:::secretbucket*", r) or is_arn_match("object", "arn:aws:s3:::othersecretbucket*", r):
                policy.add_finding("SENSITIVE_BUCKET_ACCESS", location={"action": action_name, "resource": r})

This will look for any s3 access to the buckets of interest, including not only object access such as s3:GetObject access, but also things like s3:PutBucketAcl.

Running against our test file, we'll get the following output:

./bin/parliament --file test.json --config config_override.yaml --json

{"issue": "SENSITIVE_BUCKET_ACCESS", "title": "Sensitive bucket access", "severity": "MEDIUM", "description": "Allows read access to an important S3 bucket", "detail": "", "location": {"action": "s3:GetObject", "resource": "arn:aws:s3:::secretbucket/*", "filepath": "test.json"}}

You can now decide if this specific situation is ok for you, and choose to ignore it by modifying the config_override.yaml to include:

ignore_locations:
  - filepath: "test.json"
    action: "s3:GetObject"
    resource: "arn:aws:s3:::secretbucket/\\*"

Notice that I had to double-escape the escape asterisk. If another policy is created, say in test2.json that you'd like to ignore, you can just append those values to the list:

ignore_locations:
  - filepath: "test.json"
    action: "s3:GetObject"
    resource: "arn:aws:s3:::secretbucket/\\*"
  - filepath: "test2.json"
    action: "s3:GetObject"
    resource: "arn:aws:s3:::secretbucket/\\*"

Or you could do:

ignore_locations:
  - filepath:
    - "test.json"
    - "test2.json"
    action: "s3:GetObject"
    resource: "arn:aws:s3:::secretbucket/\\*"

Unit tests for private auditors

To create unit tests for our new private auditor, create the directory ./parliament/private_auditors/tests/ and create the file test_sensitive_bucket_access.py there with the contents:

from parliament import analyze_policy_string

class TestCustom():
    """Test class for custom auditor"""

    def test_my_auditor(self):
        policy = analyze_policy_string(
            """{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::secretbucket/*"}}""",
        )
        assert_equal(len(policy.findings), 1)

That test ensures that for the given policy (which is granting read access to our secret bucket) one finding will be created.

Now when you run ./tests/scripts/unit_tests.sh there should be one additional test run.

Community auditors

  • The process for community auditors is the same as private auditors, except that:
  • The community auditors are located in the parliament/community_auditors folder instead of the parliament/private_auditors
  • The community auditors are bundled with the package and users can include them in their testing by specifying --include-community-auditors flag.

Development

Setup a testing environment

python3 -m venv ./venv && source venv/bin/activate
pip3 install -r requirements.txt

Run unit tests with:

make test

Run locally as:

bin/parliament

Updating the privilege info

The IAM data is obtained from scraping the docs here and parsing this information with beautifulsoup using ./utils/update_iam_data.py.

Projects that use Parliament

  • CloudMapper: Has functionality to audit AWS environments and will audit the IAM policies as part of that.
  • tf-parliament: Runs Parliament against terraform files
  • iam-lint: Github action for linting AWS IAM policy documents
  • Paco: Cloud orchestration tool that integrates Parliament as a library to verify a project's IAM Policies and warns about findings.
  • ConsoleMe: Web service that makes administering and using multiple AWS accounts easier, that uses Parliament for linting IAM Policies
  • iamlive: Generates IAM Policies from observing AWS calls through client-side monitoring

parliament's People

Contributors

0xdabbad00 avatar briandbecker avatar caroseuk avatar chris-sansone-angi avatar christowles avatar danhatesnumbers avatar danielpops avatar dependabot[bot] avatar dgubitosi avatar frek818 avatar jjpestacio avatar jordan-wright avatar kevinhock avatar kmcquade avatar kteague avatar kylelady avatar michael-k avatar pabhi-pete avatar piax93 avatar rjcassara- avatar ryanjarv avatar sbower avatar seth-carroll avatar steiza avatar xen0l avatar yoava333 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parliament's Issues

Feature request: Plugin-like capabilities for checking bad combinations

Hey @0xdabbad00 ,

I was looking at the check_for_bad_patterns method and thought that it might be a good idea if Parliament supported some kind of plugin-ish way of developing your own rules in Python. I'm all for submitting PRs, but I think it would help the community contribute a lot more if we were able to add our own rules somehow. I hesitate to make a suggestion on how exactly to do that - but I think it's fair to say that the logic around policy analysis is complex enough that we'd want to express our rules in Python as well.

Let me know what you think. Happy to collaborate and/or brainstorm with you on this.

Feature: When comparison types are compatible, but not ideal, report a different finding that is LOW severity

A policy like =>

{
    "Statement": [
        {
            "Action": [
                "ec2:TerminateInstances"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
                }
            }
        }
    ]
}

Results in a finding =>

{"issue": "MISMATCHED_TYPE", "title": "Mismatched type", "severity": "MEDIUM", "detail": "Type mismatch: StringEquals requires a value of type String but given Arn", "location": {"location": {"ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"}}}

I think this is an incorrect finding, because I was expecting to be able to use StringEquals for a field that contains an ARN, but I could very well be mistaken. Raising the issue to see your thoughts.

RESOURCE_MISMATCH not correct for redshift:GetClusterCredentials

It seems like there is a problem with the logic in is_arn_match. I ran into an issue with redshift:GetClusterCredentials.

Simple repro =>

$ parliament --string '
{
    "Statement": [
        {
            "Action": "redshift:GetClusterCredentials",
            "Effect": "Allow",
            "Resource": "arn:aws:redshift:us-west-2:123456789012:dbuser:the_cluster/the_user"
        }
    ],
    "Version": "2012-10-17"
}' --json | jq .
{
  "issue": "RESOURCE_MISMATCH",
  "title": "No resources match for the given action",
  "severity": "MEDIUM",
  "description": "Double check that the actions are valid for the specified resources (and vice versa). See https://iam.cloudonaut.io/reference/ or the AWS docs for details",
  "detail": [
    {
      "action": "redshift:GetClusterCredentials",
      "required_format": "arn:*:redshift:*:*:dbuser:*/*"
    }
  ],
  "location": {
    "actions": [
      "redshift:GetClusterCredentials"
    ],
    "filepath": null
  }
}

The expected and actual pattern looks correct to me =>

(Pdb) arn_parts
['arn',    '*',   'redshift',          '*',                   '*',              'dbuser',                  '*/*']
(Pdb) resource_parts
['arn', 'aws', 'redshift', 'us-west-2', '123456789012', 'dbuser', 'the_cluster/the_user']

All the logic in is_arn_match confuses me. I feel like there could be a simple glob check at the top to circumvent all that custom logic for special things like s3 buckets. However, interestingly, that doesn't work either???

>>> fnmatch.fnmatch('arn:*:redshift:*:*:dbuser:*/*', 'arn:aws:redshift:us-west-2:528741615426:dbuser:the_cluster/the_user')
False

Which is weird, because translating to regex, then using that does work...

>>> import re
>>> re.match(fnmatch.translate('arn:*:redshift:*:*:dbuser:*/*'), 'arn:aws:redshift:us-west-2:528741615426:dbuser:the_cluster/the_user').group()
'arn:aws:redshift:us-west-2:528741615426:dbuser:the_cluster/the_user'

Feature: Command-line filters

Mentioned in #57, @kmcquade raised the idea of having filtering at the command-line such as:

parliament --exclude informational,community,whatevergroup

Right now, you can specify --minimum_severity INFO

Each rule I have been giving a grouping, such as group: INVALID. Currently, these aren't used for anything. I could rename this to tag and allow multiple tags. I could also have the exclude be able to work off of the key, severity, and these tags. I think it make sense to keep this as a basic list and not try to do any logic (ex. don't worry about "If informational AND community". If someone wants more advanced logic they can just output as json and run it through jq or something else for more logic.

Don't short-circuit checks (one issue can in some cases hide additional findings)

Use Case
I have a IAM Policy with say invalid service name "servicedoesntexist", invalid action "iamactiondoesntexists" and invalid condition key "conditionkeydoesntexist".

Observed Output
Parliament returns on the first finding invalid service "UNKNOWN_PREFIX" and returns a single finding.

Desired Output
Parliament continues and identifies all three findings. This way we can catch all issues with the IAM Policy in the pipeline, rather than iteratively updating and deploying to the pipeline to find each error.

RESOURCE_MISMATCH for ec2:RunInstances

This policy from a blog post (https://aws.amazon.com/blogs/security/working-backward-from-iam-policies-and-principal-tags-to-standardized-names-and-tags-for-your-aws-resources/) has findings:

{
  "Version": "2012-10-17",
  "Statement": [
    {       
      "Sid": "AllowEC2ResourceCreationWithRequiredTags",
      "Action": [
          "ec2:CreateVolume",
          "ec2:RunInstances"
      ],      
      "Resource": [
          "arn:aws:ec2:*:*:instance/*",
          "arn:aws:ec2:*:*:volume/*"
      ],      
      "Effect": "Allow",
      "Condition": {
          "StringEquals": {
              "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}",
              "aws:RequestTag/access-application": "${aws:PrincipalTag/access-application}",
              "aws:RequestTag/access-environment": "${aws:PrincipalTag/access-environment}",
              "aws:RequestTag/cost-center": "${aws:PrincipalTag/cost-center}"
          }
      }
  },
  {       
      "Sid": "AllowCreateTagsIfRequestingValidTags",
      "Action": [
          "ec2:CreateTags"
      ],
      "Resource": [
          "arn:aws:ec2:*:*:instance/*",
          "arn:aws:ec2:*:*:volume/*"
      ],
      "Effect": "Allow",
      "Condition": {
          "StringEquals": {
              "aws:RequestTag/access-project": "${aws:PrincipalTag/access-project}",
              "aws:RequestTag/access-application": "${aws:PrincipalTag/access-application}",
              "aws:RequestTag/access-environment": "${aws:PrincipalTag/access-environment}",
              "ec2:CreateAction": "RunInstances"
          }
      }
  }
  ]
}

This results in:

{"issue": "RESOURCE_MISMATCH", "title": "No resources match for the given action", "severity": "MEDIUM", "description": "", "detail": [{"action": "ec2:RunInstances", "required_format": "arn:*:ec2:*::image/*"}, {"action": "ec2:RunInstances", "required_format": "arn:*:ec2:*:*:network-interface/*"}, {"action": "ec2:RunInstances", "required_format": "arn:*:ec2:*:*:security-group/*"}, {"action": "ec2:RunInstances", "required_format": "arn:*:ec2:*:*:subnet/*"}], "location": {"actions": ["ec2:CreateVolume", "ec2:RunInstances"], "filepath": "test.json"}}

"No resources match" - might be inaccurate due to resource type comparison

This is related to #9, but I'm opening it up as a separate issue because I think #9 is a possible root cause, but this one is the symptom.

I ran Parliament on this policy:

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_my-sec-creds-self-manage.html

And got this error:

INVALID - No resources match for iam:CreateVirtualMFADevice which requires a resource format of arn:*:iam::*:mfa/*/* for the resource mfa* - {'filepath': None}

Which I believe is inaccurate:

https://docs.aws.amazon.com/IAM/latest/UserGuide/list_identityandaccessmanagement.html#identityandaccessmanagement-resources-for-iam-policies

image

Feature: Public Auditors

@0xdabbad00 - I love the "Private Auditors" feature. It is really easy to add on new rules.

I'd like to request a "Public Auditors" feature (perhaps just call it "Public Rulesets"?) - where we can add on rulesets to this public repository to be incorporated as part of the package by default. I have a lot of tests I'd like to offload from Policy Sentry and move over to here so we can use Parliament as the primary place for static analysis for IAM policies :)

Also, command line toggling of finding groups and severity types would be legit. Like this:

parliament --exclude informational,community,whatevergroup

I have quite a few rules I'd like to check in as PRs once this is done.

Validate Statement Id values

If set, the Sid field in IAM policies may only contain characters from (A-Z,a-z,0-9). parliament should declare a finding if Sid has spaces or other invalid characters.

Installation from pip doesn't work out of the box

The tool is non-functional when following the installation instructions, due to a missing iam_definition.json file from whichever folder you run the tool from:

dpopes@dev62-uswest1adevc (devbox):~ [09:50:33] โœ“
$ virtualenv --python=python3.6 ./venv-parliament
Running virtualenv with interpreter /usr/bin/python3.6
Using base prefix '/usr'
New python executable in /nail/home/dpopes/venv-parliament/bin/python3.6
Also creating executable in /nail/home/dpopes/venv-parliament/bin/python
Installing setuptools, pip, wheel...source done.
dpopes@dev62-uswest1adevc (devbox):~ [09:50:47] โœ“
$ source venv-parliament/bin/activate
(venv-parliament) dpopes@dev62-uswest1adevc (devbox):~ [09:50:51] โœ“
$ pip install parliament
Collecting parliament
  Downloading https://files.pythonhosted.org/packages/b3/91/c487c5c69032e71ea3340b037f71dbbd2d958dc59e809cd97bfe307f5b08/parliament-0.2.2.tar.gz
Collecting boto3
  Using cached https://files.pythonhosted.org/packages/8e/a9/1ceaeda8aa5d3effc9098ae301820e27bf54c4000ec6f8ec79f9b265c50e/boto3-1.10.19-py2.py3-none-any.whl
Collecting jmespath
  Using cached https://files.pythonhosted.org/packages/83/94/7179c3832a6d45b266ddb2aac329e101367fbdb11f425f13771d27f225bb/jmespath-0.9.4-py2.py3-none-any.whl
Processing ./.cache/pip/wheels/d9/45/dd/65f0b38450c47cf7e5312883deb97d065e030c5cca0a365030/PyYAML-5.1.2-cp36-cp36m-linux_x86_64.whl
Collecting s3transfer<0.3.0,>=0.2.0
  Using cached https://files.pythonhosted.org/packages/16/8a/1fc3dba0c4923c2a76e1ff0d52b305c44606da63f718d14d3231e21c51b0/s3transfer-0.2.1-py2.py3-none-any.whl
Collecting botocore<1.14.0,>=1.13.19
  Using cached https://files.pythonhosted.org/packages/18/e5/0f29669244ffacc15c4cec9e10d75c26e7d300e1786e79514e62373e648c/botocore-1.13.19-py2.py3-none-any.whl
Collecting python-dateutil<2.8.1,>=2.1; python_version >= "2.7"
  Using cached https://files.pythonhosted.org/packages/41/17/c62faccbfbd163c7f57f3844689e3a78bae1f403648a6afb1d0866d87fbb/python_dateutil-2.8.0-py2.py3-none-any.whl
Collecting urllib3<1.26,>=1.20; python_version >= "3.4"
  Using cached https://files.pythonhosted.org/packages/b4/40/a9837291310ee1ccc242ceb6ebfd9eb21539649f193a7c8c86ba15b98539/urllib3-1.25.7-py2.py3-none-any.whl
Collecting docutils<0.16,>=0.10
  Using cached https://files.pythonhosted.org/packages/22/cd/a6aa959dca619918ccb55023b4cb151949c64d4d5d55b3f4ffd7eee0c6e8/docutils-0.15.2-py3-none-any.whl
Collecting six>=1.5
  Using cached https://files.pythonhosted.org/packages/65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl
Building wheels for collected packages: parliament
  Building wheel for parliament (setup.py) ... done
  Created wheel for parliament: filename=parliament-0.2.2-cp36-none-any.whl size=16196 sha256=a06908b3ba7624786287a887fc4a8ca2dfa8171e666a16937169a92c50147f3e
  Stored in directory: /nail/home/dpopes/.cache/pip/wheels/49/d8/e0/9b4a536b66f1b2dd0e0a49535457c9095d065521bfaba39572
Successfully built parliament
Installing collected packages: jmespath, six, python-dateutil, urllib3, docutils, botocore, s3transfer, boto3, pyyaml, parliament
Successfully installed boto3-1.10.19 botocore-1.13.19 docutils-0.15.2 jmespath-0.9.4 parliament-0.2.2 python-dateutil-2.8.0 pyyaml-5.1.2 s3transfer-0.2.1 six-1.13.0 urllib3-1.25.7
(venv-parliament) dpopes@dev62-uswest1adevc (devbox):~ [09:50:59] โœ“
$ parliament --help
Traceback (most recent call last):
  File "/nail/home/dpopes/venv-parliament/bin/parliament", line 5, in <module>
    from parliament.cli import main
  File "/nail/home/dpopes/venv-parliament/lib/python3.6/site-packages/parliament/__init__.py", line 15, in <module>
    iam_definition = json.load(open(iam_definition_path, "r"))
FileNotFoundError: [Errno 2] No such file or directory: '/nail/home/dpopes/venv-parliament/lib/python3.6/site-packages/parliament/iam_definition.json'

Community Auditor Scan issues

I did a test using one of our common policies. The library call I make is as follows -

  • If I do parliament.analyze_policy_string(POLICY_ARN, include_community_auditors=True) it does the additional scan against community rules.
  • If not, it does not scan by default using the community rules.

Now that's fine and I'm happy to add the argument.

But then, say I scan for community rules and there is an InvalidARN, the code crashes. In my opinion, it should report the Invalid ARN and continue scanning. If I do not scan for community rules, the code does not crash.

Expose config file

Users should be able to specify a location for their own config file to override the existing one, which can be used to mute issue types they don't care about.

Make private_auditors directory configurable

As it exists, the private_auditors directory must be in the same directory as the parliament code and iam_definition.json file. If you are running pip install parliament, that is going to be in a weird spot. I should have a command-line option to specify the location of this directory.

Identify line numbers in JSON documents of findings

No existing Python libraries for parsing JSON maintain a correlation between the elements and the line numbers. As a result, the "location" of a finding is given as the text of the element, which isn't very precise and can be ambiguous, especially in a hundred line policy.

Based on a comment from Paul McGuire (https://twitter.com/ptmcguire/status/1216604117826723842), it should be possible to use https://github.com/pyparsing/pyparsing to keep track of the line number.

Make custom auditor for known bad policies

There are a number of known bad policies I've found from AWS, either in their documentation or AWS Managed Policies. Ensure Parliament can find the problems from their policies.

Add directory searching functionality

A common request is to be able to pass a directory path, as opposed to a single file. I've been opposed to this because I'd rather people just do find . -name "*.json" | xargs -I {} | parliament --file {} . That's not very natural for a lot of folks and there is the argument that for large sets of files, the startup time of parliament could become annoying, which would be avoided if it just did its own directory iteration.

My main argument against adding this is I don't want to support it, because once I add directory iteration or recursion, people then want the ability to exclude certain files, etc. At which point I'm duplicating find and I'm writing code that isn't relevant to IAM linting.

If we can just add some other library that automatically adds exclusion flags and whatnot, that'd be great. Otherwise, maybe some basic functionality would be acceptable and if people want more they can send PRs.

The other big question is what flags should be used? Options would be either to match some of the ones used by find (as implemented on macos or ubuntu), or to match the ones used by some other linter.

Minimally, I think we need:

  • A directory passed in that will be recursively searched
  • Ability to limit the filenames evaluated to only those that match a pattern (or have a substring), specifically match *.json
  • Be able to exclude those file paths that match a substring

Community Auditor: IAM Policy permits * resources when actions support resource constraints

I will add this when #66 is merged.

Basically, it would be very useful to flag when IAM policies grant access to * resources. This is not necessary in some cases, since some IAM Actions do not support resource constraints (restricting to ARNs). But this check would identify when IAM Actions that DO support resource constraints are set to * resources.

I'd mark it as an INFO level finding, since that doesn't necessarily constitute a security issue or violation of least privilege (more often than not, it does, but we wouldn't want to assume).

Feature: Standardize and improve the location element of the findings

This seems like a bug to have results like this with the extra nested location element =>

parliament --string '''{
    "Statement": [
        {
            "Action": [
                "ec2:TerminateInstances"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
                }
            }
        }
    ],
    "Version": "2012-10-17"
}''' --json | jq .
{
  "issue": "MISMATCHED_TYPE",
  "title": "Mismatched type",
  "severity": "MEDIUM",
  "description": "",
  "detail": "Type mismatch: StringEquals requires a value of type String but given Arn",
  "location": {
    "location": {
      "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
    },
    "filepath": null
  }
}

The {"location": { "location": {"stringequals"}} seems like a bug, and I think the desired output should be more like

parliament --string '''{
    "Statement": [
        {
            "Action": [
                "ec2:TerminateInstances"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
                }
            }
        }
    ],
    "Version": "2012-10-17"
}''' --json | jq .

{
  "issue": "MISMATCHED_TYPE",
  "title": "Mismatched type",
  "severity": "MEDIUM",
  "description": "",
  "detail": "Type mismatch: StringEquals requires a value of type String but given Arn",
  "location": {
    "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role",
    "filepath": null
  }
}

Or possibly even better would be to include the Condition section and the comparison type in the finding, e.g.

parliament --string '''{
    "Statement": [
        {
            "Action": [
                "ec2:TerminateInstances"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
                }
            }
        }
    ],
    "Version": "2012-10-17"
}''' --json | jq .

{
  "issue": "MISMATCHED_TYPE",
  "title": "Mismatched type",
  "severity": "MEDIUM",
  "description": "",
  "detail": "Type mismatch: StringEquals requires a value of type String but given Arn",
  "location": {
    "Condition": {
      "StringEquals": {
        "ec2:InstanceProfile": "arn:aws:iam::123456789012:instance-profile/my_role"
      }
    },
    "filepath": null
  }
}

Fix docs for ignore_locations

The docs currently show:

ignore_locations:
    filepath:
      - testa.json
      - .*.py

It needs to show:

ignore_locations:
- filepath:
      - testa.json
      - .*.py

No resources match for lambda:AddLayerVersionPermission

Hi,

I've been testing parliament and I've found what I believe is an issue.
I was not expecting it to fail agains this policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "TestPol",
            "Effect": "Allow",
            "Action": "lambda:AddLayerVersionPermission",
            "Resource": "arn:aws:lambda:*:123456789012:layer:sol-*:*"
        }
    ]
}
# parliament --file policy3.json
INVALID - No resources match for lambda:AddLayerVersionPermission which requires a resource format of arn:*:lambda:*:*:layer:*:* for the resource layerVersion* - {'filepath': None}

On the DOCs we have

arn:${Partition}:lambda:${Region}:${Account}:layer:${LayerName}:${LayerVersion}

Do I have to have explicit * instead of a account if and layer name?

Misses cases where resources are allowed for actions in separate statements.

I tested against a tagging policy - taken straight from the AWS documentation here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ExamplePolicies_EC2.html#iam-example-runinstances-tags
Parliament gave a number of warnings about missing Resources - the reason is that the policy document splits the resources across two statements. One set of resources that don't require the Tag and another set that do.
Is there any way to account for this type of policy document in the evaluation logic?

{
  "Version": "2012-10-17", 
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
         "ec2:RunInstances"
      ],
      "Resource": [
         "arn:aws:ec2:*::image/*",
         "arn:aws:ec2:*:*:subnet/*",
         "arn:aws:ec2:*:*:network-interface/*",
         "arn:aws:ec2:*:*:security-group/*",
         "arn:aws:ec2:*:*:key-pair/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
         "ec2:RunInstances"
      ],
      "Resource": [
          "arn:aws:ec2:*:*:volume/*",
          "arn:aws:ec2:*:*:instance/*"
      ],
      "Condition": {
         "StringEquals": {
             "aws:RequestTag/environment": "production" ,
             "aws:RequestTag/purpose": "webserver"
          },
          "ForAllValues:StringEquals": {
              "aws:TagKeys": ["environment","purpose"]
          }
       }
    },
    {
      "Effect": "Allow",
      "Action": [
         "ec2:CreateTags"
      ],
      "Resource": "arn:aws:ec2:*:*:*/*",
      "Condition": {
         "StringEquals": {
             "ec2:CreateAction" : "RunInstances"
          }
       }
    }
  ]
}

Example using private auditors as a library

Is there a way to have a directory of private auditors that lives outside of the parliament folder (Iโ€™m using parliament as a library)?

The parliament module name seems hard coded in the import path in policy.py line 267

Thanks for a great library!

Break out underlying logic from _check_condition and _check_principal into separate functions for library consumption

The underlying logic behind _check_principal and _check_condition is gold. However, I would like to use Parliament to validate the logic of IAM policy subsections - specifically the principals block and condition blocks - but as-is, Parliament does not expose that underlying logic. I'd like to run this idea by you before making any kind of PR, so that if I do, I take an approach that is mutually agreeable and fits with your vision for Parliament.

For instance - this code snippet is part of the _check_condition method under the Statement class. I'd like to turn this into a method titled is_valid_operator instead.

Existing code:

        operator_type_requirement = None
        for documented_operator in OPERATORS:
            op = documented_operator.lower()
            if operator.lower() in [
                op,
                op + "ifexists",
                "forallvalues:" + op,
                "foranyvalue:" + op,
                "forallvalues:" + op + "ifexists",
                "foranyvalue:" + op + "ifexists",
            ]:
                operator_type_requirement = OPERATORS[documented_operator]
                break

Implementation-wise, we'd have to decouple the logic from the self.add_finding calls for this to be successful. But it would be extremely helpful to other libraries/tools that want to use Parliament to parse/validate subsections of IAM policies.

Let me know what you think.

Assign some kind of identifier to rules and enable allow-listing or deny-listing violations

We have hundreds of policies, and many of them have benign violations. For example, any policy statement with s3:* on all bucket/object resources outputs:

e.g.

                "Statement": [
                    {
                        "Action": "s3:*",
                        "Effect": "Allow",
                        "Resource": [
                            "arn:aws:s3:::the_bucket",
                            "arn:aws:s3:::the_bucket/*",
                        ]
                    }
                ],

outputs things about Job related actions that I personally don't care about.

INVALID - No resources match for s3.CreateJob which requires a resource format of * - {'filepath': None}
INVALID - No resources match for s3:DescribeJob which requires a resource format of arn:*:s3:*:*:job/* for the resource job* - {'filepath': None}
INVALID - No resources match for s3.GetAccountPublicAccessBlock which requires a resource format of * - {'filepath': None}
INVALID - No resources match for s3.ListAllMyBuckets which requires a resource format of * - {'filepath': None}
INVALID - No resources match for s3.ListJobs which requires a resource format of * - {'filepath': None}
INVALID - No resources match for s3.PutAccountPublicAccessBlock which requires a resource format of * - {'filepath': None}
INVALID - No resources match for s3:UpdateJobPriority which requires a resource format of arn:*:s3:*:*:job/* for the resource job* - {'filepath': None}
INVALID - No resources match for s3:UpdateJobStatus which requires a resource format of arn:*:s3:*:*:job/* for the resource job* - {'filepath': None}

Currently all I can do is grep -v on substrings, but that's not great. It would be nice if, similar to flake8 or pep8 rules, each violation type had an identifier and they could be allow-listed or deny-listed.

Ensure deny by IP has a condition for aws:ViaAWSService

If someone wants to try to deny access to AWS, except from certain IPs, I think in all cases they will want to include a condition to ignore situations where a call is being made via aws:ViaAWSService
See the example here:
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_deny-ip.html

It may be unlikely that Parliament would end up checking this policy before it was deployed, but it could help some people avoid breaking their environments.

Write glob intersection library

There needs to be a library written that can check for glob intersections. Some test cases:

[
# string1, string2, whether they match
{"a", "b", False},
{"a", "a", True},
{"a", "*", True},
{"*", "a", True},
{"a*a", "*", True},
{"a*a", "a*b", False},
{"a*a", "aa", True},
{"a*a", "aba", True},
{"*a*", "*b*", True}, # Example "ab"
{"a*a*", "a*b*", True}, # Example "aba"
]

This would have better fixed #32 and #11 instead of me endlessly hacking is_arn_match

Ensure parliament is only installed on python3

If you run pip install parliament under python 2.7, it apparently fails giving an error message that doesn't explain the problem well. I should see if there is a way to make pip understand that parliament only works on python 3, or to die more cleanly in a way that better explains the problem.

Create resource policy specific checks

If the policy contains a Principal, we can assume it is a Resource policy. Also, #14 (where we allow the user to specify the type of policy) would inform this.

Once we know it is a resource policy, there are certain checks we can perform. For example, given this policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
				"sns:Publish",
				"sns:DeleteTopic",
            ],
            "Resource": [
                "arn:aws:sns:us-east-1:000000000000:myTopic"
            ],
            "Principal": "*",
            "Condition": {
			      "ArnEquals": {
			        "aws:SourceArn": "arn:aws:s3:::mybucket"
			      }
    		}
        }
    ]
}

Some things to check for:

  1. If Principal is * without certain conditions, create finding.
  2. If Condition restricts to an S3, but doesn't have a SourceAccount, create finding (see https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-use-cases.html#sns-allow-s3-bucket-to-publish-to-topic)
  3. If condition restricts to an S3, and Resource is an SNS, then the Actions should only be sns:Publish

Provide __is_allowed method as directly callable (i.e., get_allowed_actions)

Scott, it would be helpful if the __is_allowed method here was available outside of the get_allowed_resources method (see here). I like that Parliament considers Conditions etc. when evaluating whether an action is actually allowed or not (whereas in Policy Sentry I just expand the actions and then see if the action is in the list of expanded actions). But it looks like there is no way to just feed in the policy and determine if an action is allowed.

iq not accepted as a permission

Using iq:* and iq-permission:* is flagged as invalid. This is because no actions exist for those privilege prefixes. I should better identify why these privileges are not ok.

get-account-authorization-details file analysis is not working

I put together a gist to help reproduce, with a sanitized example json file.

https://gist.github.com/kmcquade/a809fff851371ffffe7bdb70bf5e1965

Command:

parliament --auth-details-file example-get-account-authorization-details.json --private_auditors tmp/parliament_auditors

Response:

Traceback (most recent call last):
  File "/Users/kmcquade/Library/Python/3.7/bin/parliament", line 8, in <module>
    sys.exit(main())
  File "/Users/kmcquade/Library/Python/3.7/lib/python/site-packages/parliament/cli.py", line 180, in main
    json.dumps(version["Document"]), role["Arn"], private_auditors_custom_path=args.private_auditors
UnboundLocalError: local variable 'version' referenced before assignment

I made a fix for the issues in handling that file, so I will make a PR shortly.

expand_action can't handle service *

When given an admin policy (*:*), expand_action is not able to produce a full list of actions. Error produced is:

    parliament.UnknownPrefixException: Unknown prefix *

Add support for inappropriate condition operators

Source, your training and this link https://stackoverflow.com/questions/36413759/s3-bucket-security-open-access-restricted-by-public-ip-and-vpc-endpoint/46850897#46850897

Bad example:

{
  "Version": "2012-10-17",
  "Statement": {
    "Sid": "THISPOLICYDOESNOTWORK",
    "Effect": "Allow",
    "Action": "ec2:RunInstances",
    "Resource": "*",
    "Condition": {"StringLike": {"ec2:InstanceType": [
      "t1.*",
      "t2.*",
      "m3.*"
    ]}}
  }
}

Good example:

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "ec2:RunInstances",
    "Resource": "*",
    "Condition": {"StringLikeIfExists": {"ec2:InstanceType": [
      "t1.*",
      "t2.*",
      "m3.*"
    ]}}
  }
}

Add verbose flag

If there are no findings, Parliament shows no output. This can make it difficult to understand if it actually audited any files, or especially if the private auditors were used. Display extra output, to stderr maybe, to show that info.

Zero return code when an invalid policy is encountered

I've just started exploring this tool and how it might be incorporated into a pipeline test phase. Currently I am using version 0.2.6

Is the zero return code when an invalid policy is encountered by design?

Using the example in the readme:

$ parliament --string '{"Version":"2012-10-17","Statement": {"Effect": "Allow","Action":["s3:GetObject"],"Resource": ["arn:aws:s3:::bucket1"]}}'
INVALID - No resources match for s3:GetObject which requires a resource format of arn:*:s3:::*/* for the resource object* - {'filepath': None}

$ echo $?
0

Similar tools in json/yaml linting return non-zero when an error is encountered, which makes trapping undesired states easier than parsing the output.

Community auditor: sensitive bucket access

Now, when community auditor support is available, we should start adding more checks. One of the checks that will be useful is sensitive bucket access. We could use the example from README.md as a baseline, but it would be great if the check supported a way how to specify sensitive buckets, e.g. via config.

Provide way of avoiding the community auditor requirements

The latest release with PolicySentry added for the community auditors adds a lot of additional requirements, including pandas and SQLAlchemy. This increased the size from 8.4MB (3.1MB of which is the iam definition) to more than 52MB. The size isn't so much a concern for me as the additional dependencies are. The inclusion of PolicySentry is only currently used for the rule to identify "Permissions management" APIs, which could instead just be added as a static list of a few dozen privilege names.

Strip wildcards from "resource type" field in when scraping Actions Table?

It looks like when you scrape the Actions table from the documentation, you don't strip the * from the end of the resource types.

https://github.com/duo-labs/parliament/blob/master/utils/update_iam_data.py#L123

When writing policy sentry, I was confused by this too. But I realized that:
(1) the * in that table appears to be more of an asterisk indicating "look somewhere else on this page, like click the link, to find out what it means" rather than a wildcard.
(2) It seems like the * pattern in those tables in the IAM docs is lazily and inconsistently applied.
(3) Bottom line was that for me, it would mess with my automation.

I am not sure if it is messing with yours, but when debugging Parliament for a separate issue, I saw that it would look for resource_type = "user*" here.

image

I'd submit a PR, but I'm not sure what the after effects of such a change would be, so I'm really just opening up this issue to raise a possible concern. Lmk what you think.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.