Skip to content

Instantly share code, notes, and snippets.

@minrk
Created April 15, 2021 18:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save minrk/1d7c2bdda54dc16fd5491ec4b60904dd to your computer and use it in GitHub Desktop.
Save minrk/1d7c2bdda54dc16fd5491ec4b60904dd to your computer and use it in GitHub Desktop.
#!/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()
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