parliament


Nameparliament JSON
Version 1.6.3 PyPI version JSON
download
home_pagehttps://github.com/duo-labs/parliament
Summaryparliament audits your AWS IAM policies
upload_time2024-07-26 14:14:27
maintainerNone
docs_urlNone
authorDuo Security
requires_python>=3.6
licenseBSD 3
keywords aws parliament iam lint audit
VCS
bugtrack_url
requirements attrs beautifulsoup4 boto3 botocore certifi chardet charset-normalizer coverage docutils idna iniconfig jmespath json-cfg kwonly-args packaging pluggy py pyparsing pytest pytest-cov python-dateutil PyYAML requests s3transfer six soupsieve tomli urllib3
Travis-CI
coveralls test coverage
            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](https://parliament.summitroute.com/)

# 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](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html) and parsing this information with beautifulsoup using `./utils/update_iam_data.py`.

Use a script like this to generate a new `iam_definition.json`.

```bash
python3 -m venv ./venv
source ./venv/bin/activate
pip install requests beautifulsoup4
wget "https://raw.githubusercontent.com/duo-labs/parliament/main/utils/update_iam_data.py"
python ./update_iam_data.py > iam_definition.json
```

Find the Python environment in which you installed Parliament and overwrite the old `iam_definition.json`.

# Projects that use Parliament
- [CloudMapper](https://github.com/duo-labs/cloudmapper): Has functionality to audit AWS environments and will audit the IAM policies as part of that.
- [tf-parliament](https://github.com/rdkls/tf-parliament): Runs Parliament against terraform files
- [iam-lint](https://github.com/xen0l/iam-lint): Github action for linting AWS IAM policy documents 
- [Paco](https://paco-cloud.io): Cloud orchestration tool that integrates Parliament as a library to verify a project's IAM Policies and warns about findings.
- [ConsoleMe](https://github.com/Netflix/consoleme): Web service that makes administering and using multiple AWS accounts easier, that uses Parliament for linting IAM Policies
- [iamlive](https://github.com/iann0036/iamlive): Generates IAM Policies from observing AWS calls through client-side monitoring

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/duo-labs/parliament",
    "name": "parliament",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": null,
    "keywords": "aws parliament iam lint audit",
    "author": "Duo Security",
    "author_email": "scott@summitroute.com",
    "download_url": "https://files.pythonhosted.org/packages/a6/3f/b7262b8a7c8d41c243950c5858cefc29652623599a6fafb2f753621b9702/parliament-1.6.3.tar.gz",
    "platform": null,
    "description": "parliament is an AWS IAM linting library. It reviews policies looking for problems such as:\n- malformed json\n- missing required elements\n- incorrect prefix and action names\n- incorrect resources or conditions for the actions provided\n- type mismatches\n- bad policy patterns\n\nThis 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.\n\n[demo](https://parliament.summitroute.com/)\n\n# Installation\n```\npip install parliament\n```\n\n# Usage\n```\ncat > test.json << EOF\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": {\n        \"Effect\": \"Allow\",\n        \"Action\":[\"s3:GetObject\"],\n        \"Resource\": [\"arn:aws:s3:::bucket1\"]\n    }\n}\nEOF\n\nparliament --file test.json\n```\n\nThis will output:\n```\nMEDIUM - No resources match for the given action -  - [{'action': 's3:GetObject', 'required_format': 'arn:*:s3:::*/*'}] - {'actions': ['s3:GetObject'], 'filepath': 'test.json'}\n```\n\nThis example is showing that the action s3:GetObject requires a resource matching an object path (ie. it must have a \"/\" in it).\n\nThe different input types allowed include:\n- --file: Filename\n- --directory: A directory path, for exmaple: `--directory . --include_policy_extension json --exclude_pattern \".*venv.*\"`\n- --aws-managed-policies: For use specifically with the repo https://github.com/z0ph/aws_managed_policies\n- --auth-details-file: For use with the file returned by \"aws iam get-account-authorization-details\"\n- --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/*\"]}}'\n\n## Using parliament as a library\nParliament was meant to be used a library in other projects. A basic example follows.\n\n```\nfrom parliament import analyze_policy_string\n\nanalyzed_policy = analyze_policy_string(policy_doc)\nfor f in analyzed_policy.findings:\n  print(f)\n```\n\n## Custom config file\nYou 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:\n\n```\n{\n    \"Version\": \"2012-10-17\",\n    \"Id\": \"123\",\n    \"Statement\": [\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": \"s3:abc\",\n        \"Resource\": \"*\"\n      },\n      {\n        \"Effect\": \"Allow\",\n        \"Action\": [\"s3:*\", \"ec2:*\"],\n        \"Resource\": \"arn:aws:s3:::test/*\"\n      }\n    ]\n }\n```\n\nThis will have two findings:\n- LOW - Unknown action -  - Unknown action s3:abc\n- MEDIUM - No resources match for the given action\n\nThe 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.\n\nNow create a file `config_override.yaml` with the following contents:\n\n```\nUNKNOWN_ACTION:\n  severity: MEDIUM\n  ignore_locations:\n  - filepath:\n    - testa.json\n    - .*.py\n\nRESOURCE_MISMATCH:\n  ignore_locations:\n  - actions: \".*s3.*\"\n```\n\nNow run: `parliament --file test.json --config config_override.yaml`\nYou will have only one output: `MEDIUM - Unknown action -  - Unknown action s3:abc`\n\nNotice 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,\nand then uses your string as a regex.  This means that we are checking if `s3` is in `str([\"s3:*\", \"ec2:*\"])`\n\nNow 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`.\n\nYou 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.\n\nYou can have multiple elements in `ignore_locations`.  For example,\n```\n- filepath: \"test.json\"\n  action: \"s3:GetObject\"\n  resource: \n  - \"a\"\n  - \"b\"\n- resource: \"c.*\"\n```\n\nAssuming 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.*\".\n\n# Custom auditors\n\n## Private Auditors\nThis 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`.\n\nCreate a file `test.json` with contents:\n```\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": {\n        \"Effect\": \"Allow\",\n        \"Action\": \"s3:GetObject\",\n        \"Resource\": \"arn:aws:s3:::secretbucket/*\"\n    }\n}\n```\nThis is an example of the policy we want to alert on. That policy will normally not generate any findings.\n\nCreate the file `config_override.yaml` with contents:\n\n```\nSENSITIVE_BUCKET_ACCESS:\n  title: Sensitive bucket access\n  description: Allows read access to an important S3 bucket\n  severity: MEDIUM\n  group: CUSTOM\n```\n\nIn the `parliament` directory (that contains iam_definition.json), create the directory `private_auditors` and the file `parliament/private_auditors/sensitive_bucket_access.py`\n\n\n```\nfrom parliament import is_arn_match, expand_action\n\ndef audit(policy):\n    action_resources = {}\n    for action in expand_action(\"s3:*\"):\n        # Iterates through a list of containing elements such as\n        # {'service': 's3', 'action': 'GetObject'}\n        action_name = \"{}:{}\".format(action[\"service\"], action[\"action\"])\n        action_resources[action_name] = policy.get_allowed_resources(action[\"service\"], action[\"action\"])\n    \n    for action_name in action_resources:\n        resources = action_resources[action_name]\n        for r in resources:\n            if is_arn_match(\"object\", \"arn:aws:s3:::secretbucket*\", r) or is_arn_match(\"object\", \"arn:aws:s3:::othersecretbucket*\", r):\n                policy.add_finding(\"SENSITIVE_BUCKET_ACCESS\", location={\"action\": action_name, \"resource\": r})\n```\n\nThis 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`.\n\nRunning against our test file, we'll get the following output:\n```\n./bin/parliament --file test.json --config config_override.yaml --json\n\n{\"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\"}}\n```\n\nYou can now decide if this specific situation is ok for you, and choose to ignore it by modifying the\n`config_override.yaml` to include:\n\n```\nignore_locations:\n  - filepath: \"test.json\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::secretbucket/\\\\*\"\n```\n\nNotice 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:\n\n```\nignore_locations:\n  - filepath: \"test.json\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::secretbucket/\\\\*\"\n  - filepath: \"test2.json\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::secretbucket/\\\\*\"\n```\n\nOr you could do:\n\n```\nignore_locations:\n  - filepath:\n    - \"test.json\"\n    - \"test2.json\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::secretbucket/\\\\*\"\n```\n\n## Unit tests for private auditors\n\nTo 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:\n\n```\nfrom parliament import analyze_policy_string\n\nclass TestCustom():\n    \"\"\"Test class for custom auditor\"\"\"\n\n    def test_my_auditor(self):\n        policy = analyze_policy_string(\n            \"\"\"{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": {\n        \"Effect\": \"Allow\",\n        \"Action\": \"s3:GetObject\",\n        \"Resource\": \"arn:aws:s3:::secretbucket/*\"}}\"\"\",\n        )\n        assert_equal(len(policy.findings), 1)\n```\n\nThat test ensures that for the given policy (which is granting read access to our secret bucket) one finding will be created.\n\nNow when you run `./tests/scripts/unit_tests.sh` there should be one additional test run.\n\n\n## Community auditors\n\n* The process for community auditors is the same as private auditors, except that:\n - The community auditors are located in the `parliament/community_auditors` folder instead of the `parliament/private_auditors`\n - The community auditors are bundled with the package and users can include them in their testing by specifying `--include-community-auditors` flag.\n\n# Development\nSetup a testing environment\n```\npython3 -m venv ./venv && source venv/bin/activate\npip3 install -r requirements.txt\n```\n\nRun unit tests with:\n```\nmake test\n```\n\nRun locally as:\n```\nbin/parliament\n```\n\n## Updating the privilege info\nThe IAM data is obtained from scraping the docs [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html) and parsing this information with beautifulsoup using `./utils/update_iam_data.py`.\n\nUse a script like this to generate a new `iam_definition.json`.\n\n```bash\npython3 -m venv ./venv\nsource ./venv/bin/activate\npip install requests beautifulsoup4\nwget \"https://raw.githubusercontent.com/duo-labs/parliament/main/utils/update_iam_data.py\"\npython ./update_iam_data.py > iam_definition.json\n```\n\nFind the Python environment in which you installed Parliament and overwrite the old `iam_definition.json`.\n\n# Projects that use Parliament\n- [CloudMapper](https://github.com/duo-labs/cloudmapper): Has functionality to audit AWS environments and will audit the IAM policies as part of that.\n- [tf-parliament](https://github.com/rdkls/tf-parliament): Runs Parliament against terraform files\n- [iam-lint](https://github.com/xen0l/iam-lint): Github action for linting AWS IAM policy documents \n- [Paco](https://paco-cloud.io): Cloud orchestration tool that integrates Parliament as a library to verify a project's IAM Policies and warns about findings.\n- [ConsoleMe](https://github.com/Netflix/consoleme): Web service that makes administering and using multiple AWS accounts easier, that uses Parliament for linting IAM Policies\n- [iamlive](https://github.com/iann0036/iamlive): Generates IAM Policies from observing AWS calls through client-side monitoring\n",
    "bugtrack_url": null,
    "license": "BSD 3",
    "summary": "parliament audits your AWS IAM policies",
    "version": "1.6.3",
    "project_urls": {
        "Homepage": "https://github.com/duo-labs/parliament"
    },
    "split_keywords": [
        "aws",
        "parliament",
        "iam",
        "lint",
        "audit"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "65183258de47a043dd19468387463a0264dde38c702e0b4f6ffc811a8133a914",
                "md5": "529129663922a0a6e3aefec575adbd3f",
                "sha256": "f9022ce24243701e3bcd6a1f21b30c617ab12324c6e87e34e87774be1c53efa1"
            },
            "downloads": -1,
            "filename": "parliament-1.6.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "529129663922a0a6e3aefec575adbd3f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 554075,
            "upload_time": "2024-07-26T14:13:38",
            "upload_time_iso_8601": "2024-07-26T14:13:38.383797Z",
            "url": "https://files.pythonhosted.org/packages/65/18/3258de47a043dd19468387463a0264dde38c702e0b4f6ffc811a8133a914/parliament-1.6.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a63fb7262b8a7c8d41c243950c5858cefc29652623599a6fafb2f753621b9702",
                "md5": "3bbc0d49cd6eaa733c81fcdd7f0a49a6",
                "sha256": "13e0f21048c3f2f6dbc3e90035421a7f2e35eda55c829794ec3f410140ce6740"
            },
            "downloads": -1,
            "filename": "parliament-1.6.3.tar.gz",
            "has_sig": false,
            "md5_digest": "3bbc0d49cd6eaa733c81fcdd7f0a49a6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 533356,
            "upload_time": "2024-07-26T14:14:27",
            "upload_time_iso_8601": "2024-07-26T14:14:27.717167Z",
            "url": "https://files.pythonhosted.org/packages/a6/3f/b7262b8a7c8d41c243950c5858cefc29652623599a6fafb2f753621b9702/parliament-1.6.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-07-26 14:14:27",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "duo-labs",
    "github_project": "parliament",
    "travis_ci": true,
    "coveralls": true,
    "github_actions": true,
    "requirements": [
        {
            "name": "attrs",
            "specs": [
                [
                    "==",
                    "22.1.0"
                ]
            ]
        },
        {
            "name": "beautifulsoup4",
            "specs": [
                [
                    "==",
                    "4.11.1"
                ]
            ]
        },
        {
            "name": "boto3",
            "specs": [
                [
                    "==",
                    "1.24.66"
                ]
            ]
        },
        {
            "name": "botocore",
            "specs": [
                [
                    "==",
                    "1.27.66"
                ]
            ]
        },
        {
            "name": "certifi",
            "specs": [
                [
                    "==",
                    "2023.7.22"
                ]
            ]
        },
        {
            "name": "chardet",
            "specs": [
                [
                    "==",
                    "5.0.0"
                ]
            ]
        },
        {
            "name": "charset-normalizer",
            "specs": [
                [
                    "==",
                    "2.1.1"
                ]
            ]
        },
        {
            "name": "coverage",
            "specs": [
                [
                    "==",
                    "6.4.4"
                ]
            ]
        },
        {
            "name": "docutils",
            "specs": [
                [
                    "==",
                    "0.19"
                ]
            ]
        },
        {
            "name": "idna",
            "specs": [
                [
                    "==",
                    "3.7"
                ]
            ]
        },
        {
            "name": "iniconfig",
            "specs": [
                [
                    "==",
                    "1.1.1"
                ]
            ]
        },
        {
            "name": "jmespath",
            "specs": [
                [
                    "==",
                    "1.0.1"
                ]
            ]
        },
        {
            "name": "json-cfg",
            "specs": [
                [
                    "==",
                    "0.4.2"
                ]
            ]
        },
        {
            "name": "kwonly-args",
            "specs": [
                [
                    "==",
                    "1.0.10"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "21.3"
                ]
            ]
        },
        {
            "name": "pluggy",
            "specs": [
                [
                    "==",
                    "1.0.0"
                ]
            ]
        },
        {
            "name": "py",
            "specs": [
                [
                    "==",
                    "1.11.0"
                ]
            ]
        },
        {
            "name": "pyparsing",
            "specs": [
                [
                    "==",
                    "3.0.9"
                ]
            ]
        },
        {
            "name": "pytest",
            "specs": [
                [
                    "==",
                    "7.1.3"
                ]
            ]
        },
        {
            "name": "pytest-cov",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.8.2"
                ]
            ]
        },
        {
            "name": "PyYAML",
            "specs": [
                [
                    "==",
                    "6.0"
                ]
            ]
        },
        {
            "name": "requests",
            "specs": [
                [
                    "==",
                    "2.32.2"
                ]
            ]
        },
        {
            "name": "s3transfer",
            "specs": [
                [
                    "==",
                    "0.6.0"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.16.0"
                ]
            ]
        },
        {
            "name": "soupsieve",
            "specs": [
                [
                    "==",
                    "2.3.2.post1"
                ]
            ]
        },
        {
            "name": "tomli",
            "specs": [
                [
                    "==",
                    "2.0.1"
                ]
            ]
        },
        {
            "name": "urllib3",
            "specs": [
                [
                    "==",
                    "1.26.19"
                ]
            ]
        }
    ],
    "lcname": "parliament"
}
        
Elapsed time: 0.82761s