Created
April 15, 2021 18:55
-
-
Save minrk/1d7c2bdda54dc16fd5491ec4b60904dd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
Quickly check which github actions repos might have access to your secrets, | |
in case they are compromised. | |
- Collect github workflow files for all repos in an org with graphql | |
- Crude analysis of workflow yaml files to: | |
- look for `secrets.` indicating that a secret is used | |
- show all actions via `uses:` in steps | |
""" | |
import json | |
import netrc | |
from jinja2 import Template | |
import requests | |
import requests_cache | |
import yaml | |
workflow_gql = Template( | |
""" | |
{ | |
organization(login: "{{ organization }}") { | |
repositories(first: 100{% if after -%}, after: "{{ after }}" {%- endif %}) { | |
pageInfo { | |
endCursor | |
hasNextPage | |
} | |
edges { | |
node { | |
nameWithOwner | |
object(expression: "HEAD:.github/workflows") { | |
... on Tree { | |
entries { | |
name | |
path | |
object { | |
... on Blob { | |
text | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
""" | |
) | |
requests_cache.install_cache("github", allowable_methods=["GET", "POST"]) | |
s = requests.Session() | |
auth = netrc.netrc().authenticators("api.github.com") | |
if auth: | |
print("Using netrc auth token") | |
s.headers["Authorization"] = f"bearer {auth[2]}" | |
else: | |
print("No auth") | |
github_graphql = "https://api.github.com/graphql" | |
def fetch_workflows(organization: str = "jupyterhub"): | |
query = workflow_gql.render( | |
organization=organization, | |
after=None, | |
) | |
resp = s.post(github_graphql, data=json.dumps(dict(query=query))) | |
result = resp.json() | |
if resp.status_code >= 400: | |
print(result) | |
resp.raise_for_status() | |
repos = result["data"]["organization"]["repositories"] | |
for repo_edge in repos["edges"]: | |
repo = repo_edge["node"] | |
repo_info = { | |
"name": repo["nameWithOwner"], | |
"workflows": [], | |
} | |
if repo["object"]: | |
for workflow in repo["object"]["entries"]: | |
repo_info["workflows"].append( | |
{ | |
"name": workflow["name"], | |
"path": workflow["path"], | |
"text": workflow["object"]["text"], | |
} | |
) | |
yield repo_info | |
# pagination | |
if repos["pageInfo"]["hasNextPage"]: | |
for repo_info in fetch_workflows( | |
organization, | |
after=repos["pageInfo"]["endCursor"], | |
): | |
yield repo_info | |
def analyse_workflow(workflow_yaml): | |
"""Analyze one workflow""" | |
# secret lines | |
secret_lines = {} | |
for lineno, line in enumerate(workflow_yaml.splitlines()): | |
if not line.strip().startswith("#") and "secrets." in line: | |
secret_lines[lineno + 1] = line | |
# parse the workflow | |
workflow = yaml.safe_load(workflow_yaml) | |
actions = [] | |
for job_name, job in workflow["jobs"].items(): | |
for step in job["steps"]: | |
action = step.get("uses") | |
if action: | |
actions.append(action) | |
return { | |
"secrets": secret_lines, | |
"actions": actions, | |
} | |
def main(organization="jupyterhub", verbose=False): | |
for repo in fetch_workflows(organization): | |
name = repo["name"] | |
workflows = repo["workflows"] | |
if not workflows: | |
if verbose: | |
print(f"{name}: no workflows") | |
continue | |
for workflow in workflows: | |
analysis = analyse_workflow(workflow["text"]) | |
if analysis['secrets']: | |
print(f"{name}/{workflow['path']}") | |
print(" possible secrets:") | |
for lineno, line in sorted(analysis['secrets'].items()): | |
print(f" L{lineno}: {line}") | |
print(" actions:") | |
for action in sorted(set(analysis['actions'])): | |
print(f" {action}") | |
else: | |
if verbose: | |
print(f"{name}/{workflow['path']}: no apparent secrets") | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Using netrc auth token | |
jupyterhub/jupyterhub/.github/workflows/release.yml | |
possible secrets: | |
L65: TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} | |
L95: username: ${{ secrets.DOCKERHUB_USERNAME }} | |
L96: password: ${{ secrets.DOCKERHUB_TOKEN }} | |
L109: githubToken: ${{ secrets.GITHUB_TOKEN }} | |
L128: githubToken: ${{ secrets.GITHUB_TOKEN }} | |
L148: githubToken: ${{ secrets.GITHUB_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-node@v1 | |
actions/setup-python@v2 | |
actions/upload-artifact@v2 | |
docker/build-push-action@v2 | |
docker/login-action@v1 | |
docker/setup-buildx-action@v1 | |
docker/setup-qemu-action@v1 | |
jupyterhub/action-major-minor-tag-calculator@main | |
jupyterhub/configurable-http-proxy/.github/workflows/publish.yml | |
possible secrets: | |
L24: NODE_AUTH_TOKEN: ${{ secrets.npm_token }} | |
L47: username: ${{ secrets.DOCKERHUB_USERNAME }} | |
L48: password: ${{ secrets.DOCKERHUB_TOKEN }} | |
L62: githubToken: ${{ secrets.GITHUB_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-node@v1 | |
docker/build-push-action@v2 | |
docker/login-action@v1 | |
docker/setup-buildx-action@v1 | |
docker/setup-qemu-action@v1 | |
jupyterhub/action-major-minor-tag-calculator@main | |
jupyterhub/oauthenticator/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/dockerspawner/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/batchspawner/.github/workflows/python-publish.yml | |
possible secrets: | |
L28: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
jupyterhub/kubespawner/.github/workflows/publish.yaml | |
possible secrets: | |
L57: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/ldapauthenticator/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/wrapspawner/.github/workflows/python-publish.yml | |
possible secrets: | |
L28: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
jupyterhub/jupyter-server-proxy/.github/workflows/publish.yaml | |
possible secrets: | |
L60: password: ${{ secrets.PYPI_PASSWORD }} | |
L82: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/download-artifact@v2 | |
actions/setup-node@v1 | |
actions/setup-python@v2 | |
actions/upload-artifact@v2 | |
pypa/gh-action-pypi-publish@v1.3.0 | |
jupyterhub/zero-to-jupyterhub-k8s/.github/workflows/publish.yml | |
possible secrets: | |
L86: echo "${{ secrets.JUPYTERHUB_HELM_CHART_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 | |
L99: docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKER_PASSWORD }}" | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
actions/upload-artifact@v2 | |
docker/setup-buildx-action@v1 | |
docker/setup-qemu-action@v1 | |
jupyterhub/zero-to-jupyterhub-k8s/.github/workflows/vuln-scan.yaml | |
possible secrets: | |
L187: token: "${{ secrets.GITHUB_TOKEN }}" | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
aquasecurity/trivy-action@master | |
jacobtomlinson/gha-find-replace@0.1.2 | |
peter-evans/create-pull-request@v3 | |
jupyterhub/binderhub/.github/workflows/publish.yml | |
possible secrets: | |
L49: echo "${{ secrets.JUPYTERHUB_HELM_CHART_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 | |
L62: docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKER_PASSWORD }}" | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
jupyterhub/repo2docker/.github/workflows/release.yml | |
possible secrets: | |
L34: password: ${{ secrets.PYPI_API_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@master | |
jupyterhub/mybinder.org-deploy/.github/workflows/cd.yml | |
possible secrets: | |
L118: GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }} | |
L124: username: ${{ secrets.DOCKER_USERNAME }} | |
L125: password: ${{ secrets.DOCKER_PASSWORD }} | |
L249: GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }} | |
L257: username: ${{ secrets.DOCKER_USERNAME_OVH }} | |
L258: password: ${{ secrets.DOCKER_PASSWORD_OVH }} | |
actions: | |
actions/cache@v2 | |
actions/checkout@v2 | |
actions/setup-python@v1 | |
azure/docker-login@v1 | |
azure/setup-kubectl@v1 | |
google-github-actions/setup-gcloud@master | |
nick-invision/retry@39da88d5f7d15a96aed861dbabbe8b7443e3182a | |
sliteteam/github-action-git-crypt-unlock@a09ea5079c1b0e1887d4c8d7a4b20f00b5c2d06b | |
jupyterhub/mybinder.org-deploy/.github/workflows/lint-validate.yml | |
possible secrets: | |
L36: GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }} | |
L46: export YAMLLINT_CONFIG=scripts/yamllint-no-secrets.yaml | |
L103: GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }} | |
L118: export SECRET_CONFIG="-f config/test-secrets.yaml" | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v1 | |
sliteteam/github-action-git-crypt-unlock@a09ea5079c1b0e1887d4c8d7a4b20f00b5c2d06b | |
jupyterhub/nbgitpuller/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/ltiauthenticator/.github/workflows/publish.yaml | |
possible secrets: | |
L55: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/chartpress/.github/workflows/publish.yaml | |
possible secrets: | |
L37: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/traefik-proxy/.github/workflows/release.yml | |
possible secrets: | |
L42: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/nativeauthenticator/.github/workflows/publish.yaml | |
possible secrets: | |
L37: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/jupyter-remote-desktop-proxy/.github/workflows/binder-badge.yaml | |
possible secrets: | |
L12: github-token: ${{secrets.GITHUB_TOKEN}} | |
actions: | |
actions/github-script@v1 | |
jupyterhub/jupyter-remote-desktop-proxy/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/repo2docker-action/.github/workflows/binder.yaml | |
possible secrets: | |
L15: github-token: ${{secrets.GITHUB_TOKEN}} | |
actions: | |
actions/github-script@v1 | |
jupyterhub/jupyterhub-idle-culler/.github/workflows/publish.yml | |
possible secrets: | |
L35: password: ${{ secrets.pypi_password }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
pypa/gh-action-pypi-publish@v1.4.1 | |
jupyterhub/pebble-helm-chart/.github/workflows/publish.yml | |
possible secrets: | |
L44: echo "${{ secrets.JUPYTERHUB_HELM_CHART_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 | |
actions: | |
actions/checkout@v2 | |
actions/setup-python@v2 | |
jupyterhub/action-k8s-await-workloads/.github/workflows/package.yaml | |
possible secrets: | |
L51: token: ${{ secrets.GITHUB_TOKEN }} | |
actions: | |
actions/checkout@v2 | |
actions/setup-node@v2 | |
peter-evans/create-pull-request@v3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment