mirror of
https://github.com/Relintai/gdnative_python.git
synced 2024-11-14 10:27:22 +01:00
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
|
#! /usr/bin/env python
|
||
|
#
|
||
|
# see: https://julienrenaux.fr/2019/12/20/github-actions-security-risk/
|
||
|
# TL;DR: Using GitHub actions with branch names or tags is unsafe. Use commit hash instead.
|
||
|
|
||
|
|
||
|
import re
|
||
|
import sys
|
||
|
import json
|
||
|
import argparse
|
||
|
from pathlib import Path
|
||
|
from functools import lru_cache
|
||
|
from urllib.request import urlopen
|
||
|
|
||
|
|
||
|
GITHUB_CONF_DIR = Path(".").joinpath("../.github").resolve()
|
||
|
REPO_REGEX = r"(?P<repo>[\w\-_]+/[\w\-_]+)"
|
||
|
SHA_REGEX = r"(?P<sha>[a-fA-F0-9]{40})"
|
||
|
TAG_REGEX = r"(?P<tag>[\w\-_]+)"
|
||
|
PIN_REGEX = r"(?P<pin>[\w\-_]+)"
|
||
|
USES_REGEX = re.compile(
|
||
|
rf"uses\W*:\W*{REPO_REGEX}@({SHA_REGEX}|{TAG_REGEX})(\W*#\W*pin@{PIN_REGEX})?", re.MULTILINE
|
||
|
)
|
||
|
|
||
|
|
||
|
def get_files(pathes):
|
||
|
for path in pathes:
|
||
|
if path.is_dir():
|
||
|
yield from path.rglob("*.yml")
|
||
|
elif path.is_file():
|
||
|
yield path
|
||
|
|
||
|
|
||
|
@lru_cache(maxsize=None)
|
||
|
def resolve_tag(repo, tag):
|
||
|
url = f"https://api.github.com/repos/{repo}/git/ref/tags/{tag}"
|
||
|
with urlopen(url) as f:
|
||
|
data = json.loads(f.read())
|
||
|
return data["object"]["sha"]
|
||
|
|
||
|
|
||
|
def add_pin(pathes):
|
||
|
for file in get_files(pathes):
|
||
|
txt = file.read_text()
|
||
|
overwrite_needed = False
|
||
|
# Start by the end so that we can use match.start/end to do in-place modifications
|
||
|
for match in reversed(list(USES_REGEX.finditer(txt))):
|
||
|
repo = match.group("repo")
|
||
|
tag = match.group("tag")
|
||
|
if tag is not None:
|
||
|
sha = resolve_tag(repo, tag)
|
||
|
print(f"Pinning github action {file}: {repo}@{tag} => {sha}")
|
||
|
txt = txt[: match.start()] + f"uses: {repo}@{sha} # pin@{tag}" + txt[match.end() :]
|
||
|
overwrite_needed = True
|
||
|
if overwrite_needed:
|
||
|
file.write_text(txt)
|
||
|
return 0
|
||
|
|
||
|
|
||
|
def check_pin(pathes):
|
||
|
ret = 0
|
||
|
for file in get_files(pathes):
|
||
|
for match in USES_REGEX.finditer(file.read_text()):
|
||
|
repo = match.group("repo")
|
||
|
tag = match.group("tag")
|
||
|
if tag is not None:
|
||
|
print(f"Unpinned github action {file}: {repo}@{tag}")
|
||
|
ret = 1
|
||
|
return ret
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument("cmd", choices=["check", "add"])
|
||
|
parser.add_argument(
|
||
|
"files", nargs="*", type=Path, default=[Path(__name__).joinpath("../.github/").resolve()]
|
||
|
)
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
if args.cmd == "check":
|
||
|
sys.exit(check_pin(args.files))
|
||
|
else:
|
||
|
sys.exit(add_pin(args.files))
|