Improved Filtering for Changed Rules #2712
Workflow file for this run
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
name: Rules PR CI | |
on: | |
push: | |
branches: [ "main", "test-rules" ] | |
pull_request_target: | |
branches: [ "**" ] | |
workflow_dispatch: {} | |
concurrency: | |
# For pull_request_target workflows we want to use head_ref -- the branch triggering the workflow. Otherwise, | |
# use ref, which is the branch for a push event. | |
group: ${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} | |
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }} | |
jobs: | |
tests: | |
name: Run Rule Validation | |
runs-on: ubuntu-20.04 | |
permissions: | |
contents: write | |
checks: write | |
steps: | |
- name: Set up yq | |
uses: mikefarah/[email protected] | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.head_ref }} | |
repository: ${{ github.event.pull_request.head.repo.full_name }} | |
depth: 0 | |
- uses: actions/setup-python@v4 | |
with: | |
python-version: '3.10' | |
- name: Add Rule IDs as Needed & Check for Duplicates | |
# Run before testing, just in case this could invalidate the rule itself | |
run: | | |
pip install -r scripts/generate-rule-ids/requirements.txt | |
python scripts/generate-rule-ids/main.py | |
# - name: Validate Rules | |
# run: | | |
# for f in *-rules/*.yml | |
# do | |
# echo "Processing $f" | |
# http_code=$(yq -o=json eval 'del(.type)' "$f" | curl -H "Content-Type: application/json" -X POST --data-binary @- -o response.txt -w "%{http_code}" --silent https://playground.sublimesecurity.com/v1/rules/validate) | |
# echo '' >> response.txt | |
# cat response.txt | |
# if [[ "$http_code" != "200" ]]; then | |
# echo "Unexpected response $http_code" | |
# exit 1 | |
# fi | |
# done | |
# | |
# - name: Validate Insights and Signals | |
# run: | | |
# for f in {insights,signals}/**/*.yml | |
# do | |
# echo "Processing $f" | |
# http_code=$(yq eval 'del(.type) | .source = "length([\n\n" + .source + "\n]) >= 0"' "$f" -o=json | curl -H "Content-Type: application/json" -X POST --data-binary @- -o response.txt -w "%{http_code}" --silent https://playground.sublimesecurity.com/v1/rules/validate) | |
# echo '' >> response.txt | |
# cat response.txt | |
# if [[ "$http_code" != "200" ]]; then | |
# echo "Unexpected response $http_code" | |
# exit 1 | |
# fi | |
# done | |
- name: Verify no .yaml files exist | |
run: | | |
! /bin/sh -c 'ls **/*.yaml' | |
- name: Commit & Push Results, if needed | |
run: | | |
rm response.txt | |
if [ -z "$(git status --porcelain)" ]; then | |
echo "No files changed, nothing to do" | |
exit 0 | |
fi | |
git config user.name 'ID Generator' | |
git config user.email '[email protected]' | |
git add **/*.yml | |
git commit -m "Auto add rule ID" | |
git push origin ${{ github.head_ref }} | |
- name: Get the head SHA | |
id: get_head | |
run: echo "##[set-output name=HEAD;]$(git rev-parse HEAD)" | |
- name: Get changed detection-rules | |
id: changed-files | |
uses: tj-actions/changed-files@v39 | |
with: | |
files: "detection-rules/**" | |
recover_deleted_files: true | |
- name: Checkout main | |
uses: actions/checkout@v4 | |
with: | |
# We want to filter by source in a rule changed. We always do this against main, mostly for simplicity. | |
# We don't compare all files though, only those which are identified by changed-files. | |
ref: main # TODO HEAD^ for commits to main/test-rules? | |
repository: sublime-security/sublime-rules | |
depth: 0 | |
path: sr-main | |
- name: Rename files in sr-main based on rule id | |
run: | | |
cd sr-main/detection-rules | |
for file in *.yml | |
do | |
id=$(yq '.id' "$file") | |
mv "$file" "${id}.yml" | |
done | |
pwd | |
- name: "Find updated rule IDs" | |
id: find_ids | |
run: | | |
for file in ${{ steps.changed-files.outputs.all_changed_and_modified_files }}; do | |
echo "$file was changed" | |
rule_id=$(yq '.id' $file) | |
echo "$file has rule ID $rule_id" | |
new_source=$(yq '.source' "$file") | |
old_source=$(yq '.source' "sr-main/detection-rules/$rule_id" 'source' || echo '') | |
echo $old_source | |
# We only need to care when rule source is changed. This will handle renames, tag changes, etc. | |
# We could filter out some of these using different outputs from changed-files, but easiest just to get | |
# the super set and filter on source since we'll want to do that anyway. | |
if [ "$new_source" != "$old_source" ]; then | |
echo "$file has altered source" | |
altered_rule_ids=$(echo "$rule_id"" ""$altered_rule_ids") | |
fi | |
done | |
echo "Altered Ruled IDs: [$altered_rule_ids]" | |
echo "##[set-output name=rule_ids;]$(echo $altered_rule_ids)" | |
# TODO: This doesn't solve for a modified rule_id. We could merge with any files known on 'main', but changing | |
# a rule ID is a separate problem. | |
- name: "Trigger MQL Mimic Tests" | |
env: | |
trigger_url: '${{ secrets.MQL_MOCK_TRIGGER }}' | |
branch: ${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} | |
repo: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name || github.repository }} | |
token: '${{ secrets.GITHUB_TOKEN }}' | |
sha: '${{ steps.get_head.outputs.HEAD }}' | |
only_rule_ids: '${{ steps.find_ids.outputs.rule_ids }}' | |
run: | | |
body='{"branch":"'$branch'","repo":"'$repo'","token":"'$token'","sha":"'$sha'","only_rule_ids":"'$only_rule_ids'"}' | |
echo $body | |
curl -X POST $trigger_url \ | |
-H 'Content-Type: application/json' \ | |
-d "$body" | |
# When we add a commit, GitHub won't trigger actions on the auto commit, so we're missing a required check on the | |
# HEAD commit. | |
# Various alternatives were explored, but all run into issues when dealing with forks. This sets a "Check" for | |
# the latest commit, and we can depend on that as a required check. | |
- name: "Create a check run" | |
uses: actions/github-script@v6 | |
if: github.event_name == 'pull_request_target' | |
env: | |
parameter_url: '${{ github.event.pull_request.html_url }}' | |
with: | |
debug: ${{ secrets.ACTIONS_STEP_DEBUG || false }} | |
retries: 3 | |
# Default includes 422 which GitHub returns when it doesn't know about the head_sha we set the status for. | |
# This occurs when the previous push succeeds, but the checks/pull request component of GitHub isn't yet aware | |
# of the new commit. This isn't the common case, but it comes up enough to be annoying. | |
retry-exempt-status-codes: 400, 401, 403, 404 | |
script: | | |
// any JavaScript code can go here, you can use Node JS APIs too. | |
// Docs: https://docs.github.com/en/rest/checks/runs#create-a-check-run | |
// Rest: https://octokit.github.io/rest.js/v18#checks-create | |
await github.rest.checks.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
head_sha: "${{ steps.get_head.outputs.HEAD }}", | |
name: "Rule Tests and ID Updated", | |
status: "completed", | |
conclusion: "success", | |
details_url: process.env.parameter_url, | |
output: { | |
title: "Rule Tests and ID Updated", | |
summary: "Rule Tests and ID Updated", | |
text: "Rule Tests and ID Updated", | |
}, | |
}); | |
- name: Wait for MQL Mimic check to be completed | |
uses: fountainhead/[email protected] | |
id: wait-for-build | |
# Wait for results so that the token remains valid while the test suite is executing and posting a check here. | |
with: | |
token: ${{ secrets.GITHUB_TOKEN }} | |
checkName: "MQL Mimic Tests" | |
ref: ${{ steps.get_head.outputs.HEAD }} | |
timeoutSeconds: 3600 | |