Skip to content

Commit

Permalink
refactor top-level readme, add in starter code for alert updates API (#…
Browse files Browse the repository at this point in the history
…17)

* code and doc cleanup. make a demos table on the landing readme.md
* progress on updating security alerts
  • Loading branch information
austimkelly committed May 16, 2024
1 parent eff1901 commit 9dc2271
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 73 deletions.
84 changes: 15 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,23 @@
# GHAS Scan
# GHAS Utils

This is a Python script that interacts with the GitHub API to fetch repository details and code scanning analysis information.
Make sure the repository exists and your GitHub token has the necessary permissions to access it.
# Utils

## Prerequisites
Here's a table with all the demo/utilities and what they do:

- Python 3.6 or higher
- `requests` library
> NOTE: There are not meant to be production grade scripts. They are meant to be education and to help you understand some areas where you would want to use the GitHub API.
## Installation
| Demo name | Demo description |
|-----------|-----------------|
| [ghas-org-scan](./ghas-org-scan/) | This is a sort of compliance report that builds a table of settings and security alert volumes for all repositories in an organization. This is a great way to quickly spot out-of-compliance repositories where GitHub reporting may fall short. |
| [ghas-settings](./ghas-settings/) | This is a simple demo to show you want GHAS settings you can read and write for an organization. GitHub does support https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository now, but this is good if you want to keep things programatically synchronized or have tons or orgs. |
| [pull_all_org_security_alerts](./pull_all_org_security_alerts/) | This pulls all the dependabot, secret, and code scanning alerts into 3 CSV files for an organization. |
| [pull_all_repo_security_alerts](./pull_all_repo_security_alerts/) | This just pulls security alerts and advisories for a repo. There's some extra documentation in there about the alert schemas and how to think about your security alert observability program. |
| [sbom-visualizer](./sbom-visualizer/) | This is just a quick hack to see how to parse the SBOM export from GitHub. It's nothing special here. |
| [secret-alert-pull](./secret-alert-pull/) | Pulls all secrets for an org. |
| [update-security-alerts](./update-security-alerts/) | This demo show how to update security alerts. This can be useful when needing to bulk modify hundreds or thousands of security alerts. |

1. Clone this repository:
```bash
git clone git@github.com:austimkelly/ghas-utils.git
```
2. Navigate to the cloned repository:
```bash
cd ghas-utils
```
3. Install the required Python libraries:
```bash
pip3 install requests
```
or

```bash
pip3 install -r requests.txt
```
# GitHub Personal Access Token

## Usage
All these example require the use of a personal access token. See [Setting a Personal Access Token for your Organization](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization).

1. Create a [Github Personal Access Token](https://docs.github.com/en/enterprise-server@3.6/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) and set the value in a `GITHUB_ACCESS_TOKEN` environment variable. Your personal access token will start with `github_pat_`
* This script is tested with these permissions:

![Permissions](./doc/gh_pat_permissions.png)

NOTE: For organizations which you are not an owner, please see [Setting a Personal Access Token for your Organization](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization). If you leverage a personal access token to read organization repositories, you will need to enable this policy, otherwise only public repositories will be readable. You will need one personal access token per organization.

2. Open `ghas-scan.py` in your favorite text editor.
3. Replace `owner_type` variabe value with `user` or `org`.
4. Replace `owner_name` variable value with the corresponding user or org name.
5. Set `skip_forks` to `True` if you want to omit forked repos from the results.
6. Run the script:
```bash
python3 ghas-scan.py
```

### Output and Example

Output is written to `github_data.csv` at the repository root. It looks something like this:

```
Getting list of repositories...
Fetching repo security configs...
CSV file 'github_data.csv' written successfully.
Total repositories: 16
Total public repositories: 16
Percent of repositories that are forked: 0.0%
Percent of repositories with Codeowners: 6.25%
Percent of repositories with Secrets Scanning Enabled: 12.5%
Percent of repositories with Secrets Push Protection Enabled: 12.5%
Total number of open critical and high code scanning alerts: 0
Total number of open critical dependabot alerts: 0
Done.
```

You can see an example in [./example/example_output.csv](./example/example_output.csv). This is just a basic example to give you an idea of the scehma.

# References

* [Github REST API Documentation](https://docs.github.com/en/rest)
* [Secret Scanning API](https://docs.github.com/en/rest/secret-scanning/secret-scanning)
* [Code Scanning API](https://docs.github.com/en/rest/code-scanning/code-scanning)
* [Dependabot Alerts API](https://docs.github.com/en/rest/dependabot/alerts)
* [Setting a Personal Access Token for your Organization](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization)
* [Managing Security Managers in your Organization](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/managing-security-managers-in-your-organization)
45 changes: 44 additions & 1 deletion custom-properties/custom-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,48 @@ GitHub's Custom Properties allow you to add metadata to repositories in your org
* [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization#searching-and-filtering-repositories-by-custom-properties-values) - How you can set a custom property for your organization.
* [REST API Endpoints for custom properties](https://docs.github.com/en/rest/repos/custom-properties?apiVersion=2022-11-28) - You can use the REST API to view the custom properties that were assigned to a repository by the organization that owns the repository.
* [Repository Custom Properties GA and Ruleset Improvements](https://github.blog/changelog/2024-02-14-repository-custom-properties-ga-and-ruleset-improvements/) - There's a [good video here](https://www.youtube.com/watch?v=z0CYdcqZxyQ) that explains how to use custom properties and rulesets to manage your repositories.
* [Creating rulesets for repositories in your organization](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-organization-settings/creating-rulesets-for-repositories-in-your-organization) - By [combining rulesets with custom properties](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-organization-settings/creating-rulesets-for-repositories-in-your-organization#choosing-which-repositories-to-target-in-your-organization), you can define which policies apply to which repositories in your organization. Be sure to check out some of the [ruleset-recipes](https://github.com/github/ruleset-recipes) on GitHub you can import too!

# Example Script to pull custom properties from a repository

Using the GitHub API and with your GitHub personal access token run:

``` python
python3 custom-properties.py --repo <org>/<repo> --gh_pat <your_github_personal_access_token>
```

If you have custom properties in the repo, they will be printed to the console.

``` bash
Custom Property Key:Value Pairs for repo: swell-consulting/swiss-cheese
-------------------------------
risk-level: medium
```
# Combining Custom Properties with Rulesets - A simple example

Say you have a policy with these requirements:

* Identify all repositories that are considered high risk.
* Enforce the following rules on high risk repositories:
- Require status checks to pass before merging
- Block force pushes
- Require workflows to pass before merging
- Require code scanning results to pass before merging

Before custom properties and rulesets you'd have to identify each high risk repository and then apply these policies manually in branch protection rules and other configuration files. It's much easier now to enforce at an org level.

For this requirement we would:

1. Create an org-level custom property called `risk-level` with values `unset`, `low`, `medium`, and `high`. I like to flag on `unset` as the default so I can programatically check or enforce the property.
1. Set the `risk-level` property to `high` for all high risk repositories.
1. Create an org-level ruleset by from **Import a ruleset** and import: [one-ruleset-to-rule-them-all.yaml](https://github.com/github/ruleset-recipes/blob/main/branch-rulesets/org-rulesets/one-ruleset-to-rule-them-all.json).
1. Choose the **Target repositories** option and select **Target: Dynamic list by property** and set the property to `risk-level` and the value to `high`.
1. Review and enabled the **Rules** you want to enforce.
1. Profit!

The ruleset setting for the custom property selection and rule selection look like this:

![./img/target_repos.png](./img/target_repos.png)

![./img/status_checks.png](./img/status_checks.png)

# TODO: Sample script to pull custom properties for a repository
28 changes: 28 additions & 0 deletions custom-properties/custom-properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import requests
import argparse

def fetch_custom_properties(repo, gh_pat):
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {gh_pat}",
"X-GitHub-Api-Version": "2022-11-28",
}
url = f"https://api.github.com/repos/{repo}/properties/values"
response = requests.get(url, headers=headers)
response.raise_for_status() # Raise exception if the request failed
properties = response.json()
for prop in properties:
print(f"Custom Property Key:Value Pairs for repo: {repo}")
print("-------------------------------")
if not properties:
print("The repository has no custom properties.")
else:
for prop in properties:
print(f"{prop['property_name']}: {prop['value']}")

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Fetch custom properties for a GitHub repository.')
parser.add_argument('--repo', required=True, help='Repository in the format "owner/repo"')
parser.add_argument('--gh_pat', required=True, help='GitHub Personal Access Token')
args = parser.parse_args()
fetch_custom_properties(args.repo, args.gh_pat)
Binary file added custom-properties/img/status_checks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added custom-properties/img/target_repos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions ghas-org-scan/ghas-org-scan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
This is a Python script that interacts with the GitHub API to fetch repository details and code scanning analysis information.
Make sure the repository exists and your GitHub token has the necessary permissions to access it.

## Prerequisites

- Python 3.6 or higher
- `requests` library

## Installation

1. Clone this repository:
```bash
git clone git@github.com:austimkelly/ghas-utils.git
```
2. Navigate to the cloned repository:
```bash
cd ghas-utils
```
3. Install the required Python libraries:
```bash
pip3 install requests
```
or

```bash
pip3 install -r requests.txt
```

## Usage

1. Create a [Github Personal Access Token](https://docs.github.com/en/enterprise-server@3.6/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) and set the value in a `GITHUB_ACCESS_TOKEN` environment variable. Your personal access token will start with `github_pat_`
* This script is tested with these permissions:

![Permissions](./doc/gh_pat_permissions.png)

NOTE: For organizations which you are not an owner, please see [Setting a Personal Access Token for your Organization](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization). If you leverage a personal access token to read organization repositories, you will need to enable this policy, otherwise only public repositories will be readable. You will need one personal access token per organization.

2. Open `ghas-scan.py` in your favorite text editor.
3. Replace `owner_type` variable value with `user` or `org`.
4. Replace `owner_name` variable value with the corresponding user or org name.
5. Set `skip_forks` to `True` if you want to omit forked repos from the results.
6. Run the script:
```bash
python3 ghas-scan.py
```

### Output and Example

Output is written to `github_data.csv` at the repository root. The console output will look like this:

```
Getting list of repositories...
Fetching repo security configs...
CSV file 'github_data.csv' written successfully.
Total repositories: 16
Total public repositories: 16
Percent of repositories that are forked: 0.0%
Percent of repositories with Codeowners: 6.25%
Percent of repositories with Secrets Scanning Enabled: 12.5%
Percent of repositories with Secrets Push Protection Enabled: 12.5%
Total number of open critical and high code scanning alerts: 0
Total number of open critical dependabot alerts: 0
Done.
```

You can see an example CSV in [./example/example_output.csv](./example/example_output.csv). This is just a simple example to give you an idea of the schema.

# References

* [Github REST API Documentation](https://docs.github.com/en/rest)
* [Secret Scanning API](https://docs.github.com/en/rest/secret-scanning/secret-scanning)
* [Code Scanning API](https://docs.github.com/en/rest/code-scanning/code-scanning)
* [Dependabot Alerts API](https://docs.github.com/en/rest/dependabot/alerts)
* [Managing Security Managers in your Organization](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/managing-security-managers-in-your-organization)
2 changes: 2 additions & 0 deletions ghas-settings/ghas-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ See [required-ghas-settings.json](./required-ghas-settings.json) for a file that

Here's a full run to read and write out the desired security settings for an organization:

> NOTE: The application will prompt you if you want to write the new settings. You should only test this on a low-stakes organization or a test organization.
```bash
% python3 ghas-settings.py {ORG} github_pat_YOURTOKEN --verbose --org-security-settings ./required-ghas-settings.json
```
Expand Down
1 change: 0 additions & 1 deletion ghas-settings/ghas-settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import argparse
import requests
import pprint
import sys
import json

Expand Down
43 changes: 43 additions & 0 deletions pull_all_org_security_alerts/fetch-org-alerts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
This demo shows how to pull all security alerts for an organization. Because security alerts for dependabot, secrets, and code scanning all have a different schema, we need to use different queries to pull them.

## API References

* [Dependabot Alerts REST API](https://docs.github.com/en/rest/dependabot/alerts?apiVersion=2022-11-28)
* [Code Scanning Alerts for your Organization REST API](https://docs.github.com/en/rest/code-scanning/code-scanning?apiVersion=2022-11-28#list-code-scanning-alerts-for-an-organization)
* [Secret Scanning Alerts for your Organization REST API](https://docs.github.com/en/rest/secret-scanning/secret-scanning?apiVersion=2022-11-28#list-secret-scanning-alerts-for-an-organization)

## Running the demo

> NOTE: Running on private/internal repos may need to change the code to filter on the repo visibility. This is currently tested on public repos.
`python3 fetch-org-alerts.py <org name> <your github PAT>`

If you have it all set up right, you will get this output:

```
Dependabot alerts for swell-consulting: 15
Writing alert to _reports/swell-consulting_dependencies_20240516090913.csv
Code scanning alerts for swell-consulting: 19
Writing alert to _reports/swell-consulting_code_scanning_20240516090913.csv
Secret scanning alerts for swell-consulting: 1
Writing alert to _reports/swell-consulting_secrets_20240516090913.csv
Number of active and open secrets for swell-consulting: 0
Number of open critical alerts for dependencies for swell-consulting: 2
Number of open critical alerts for code scanning for swell-consulting: 19
```

As you can see from the console output, each REST API call writes results to a CSV file in the `_reports` directory.

### Example Output

> NOTE: The full schema is available. There has been no filtering on columns for this output.
See the [example_outputs directory](./example_outputs/) for an example of the output file.

## Use cases

While the Security Overview at the Enterprise and Organization level can give you some aggrated metrics on the volume of alerts and access to the raw alerts, there are some cases where you own policy may need to be applied to programmatic access on process of alert data. Some examples include:

* SLA adherence. You may want to track which alert classifications are out of SLA and take some additional action.
* Mass updates. You need to make some decision to change the status of masses of alerts.
* Deferring alerts. You may want to close some alerts with comments to defer them. Currently, there is no way to set a status to "defer" or otherwise track alerts that you want to revisit at a later date.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from datetime import datetime, timezone
import os
import pandas as pd
import json
import collections

def generate_report(org, secrets_file, dependencies_file, code_scanning_file):
Expand Down
9 changes: 8 additions & 1 deletion update-security-alerts/update-security-alerts.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
You can update security alerts for a particular alert number in a specific repository. There are 3 different APIs that will you to modify existing alerts:

> WARNING: This is a working progress. The script is not fully functional yet.
> Security alerts do not have the ability to add custom tags or custom resolutions. Additionally, there is no mechanism to snooze, silence or otherwise defer alerts for a later time. If you need to track these types of actions, you may want to consider automating specific text in the resolution/dismissed comments.
1. [Update a Dependabot alert](https://docs.github.com/en/rest/dependabot/alerts?apiVersion=2022-11-28#update-a-dependabot-alert) - When updating a Dependabot alert, you can update the `state`, `dismissed_reason`, and `dismissed_comment` fields.
1. [Update a code scanning alert](https://docs.github.com/en/rest/code-scanning/code-scanning?apiVersion=2022-11-28#update-a-code-scanning-alert) - When updating a code scanning alert, you can update the `state`, `dismissed_reason`, and `dismissed_comment` fields.
1. [Update a secret scanning alert](https://docs.github.com/en/rest/secret-scanning/secret-scanning?apiVersion=2022-11-28#update-a-secret-scanning-alert - Here you can update the `state`, `resolution`, and `resolution_comment` fields.

# TODO: Example python script . . .
# Running the sample script

``` bash
python3 update-security-alerts.py --repo "OWNER/REPO" --gh_token "YOUR-TOKEN" --alert_type "dependabot" --state "dismissed" --dismissed_reason "tolerable_risk" --dismissed_comment "This alert is accurate but we use a sanitizer." --alert_number "ALERT_NUMBER"
```

Loading

0 comments on commit 9dc2271

Please sign in to comment.