Skip to content

Commit

Permalink
workflows: add safe-upload-artifacts action
Browse files Browse the repository at this point in the history
Add safe-upload-artifacts to combine the functionality of both the
mask_secrets and upload-artifact actions.

Signed-off-by: Mike Szczys <mike@golioth.io>
  • Loading branch information
szczys committed Sep 18, 2024
1 parent 1a08878 commit b235ae4
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/actions/safe-upload-artifacts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Safe Upload Artifacts Github Action

Upload artifacts from a given set of paths. Before the archive is
uploaded the files will be scanned for GitHub secrets and masked when
found with `***NAME_OF_SECRET***`. This prevents secrets from appearing
in publicly-downloadable archives attached to workflow runs.

## Known Issues

The regex used for the replacement cannot be applied to secrets that
have linefeed characters in them. These secrets will be skipped without
notice.

## Usage

```
- name: Safe upload artifacts
id: safe-upload-artifacts
if: always()
uses: ./.github/actions/safe-upload-artifacts
with:
secrets-json: ${{ toJson(secrets) }}
name: name-for-the-uploaded-archive
path: |
path-to/file-to-archive.log
```

- The `uses` path may change based on how your workflow checks out the
repository. (eg: `uses:
./modules/lib/golioth-firmware-sdk/.github/actions/safe-upload-artifacts`).
- Secrets must be passed as serialized JSON as in the example above.
This is because actions cannot inherit secrets. Reusable workflows can
inherit secrets but they cannot be run as steps (only as jobs).
70 changes: 70 additions & 0 deletions .github/actions/safe-upload-artifacts/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Mask secrets in files

description: |
Search all files in a given path(s) and replace any GitHub secrets with ***NAME_OF_SECRET***
inputs:
secrets-json:
description: 'Secrets context to be masked, in JSON format'
required: true
name:
description: 'String to use as the archive name'
required: true
path:
description: 'Path(s) to the files to be include in the upload'
required: true

runs:
using: composite
steps:
- name: Check for installed commands
shell: bash
run: |
if ! command -v jq; then
apt update && apt install -y jq
if ! command -v jq; then
echo "Could not install command: jq"
exit 1
fi
fi
- name: Find and mask
id: find-and-mask
shell: bash
env:
SECRETS_CONTEXT: '${{ inputs.secrets-json }}'
run: |
rm -rf __grep_search_output.txt
# Enable globbing
shopt -s globstar
for key in $(jq -r "keys[]" <<< "$SECRETS_CONTEXT");
do
secret_val=$(jq -r ".$key" <<< "$SECRETS_CONTEXT")
if [[ ! $secret_val =~ "\n" ]]; then
# This approach to escaping the regex found: https://stackoverflow.com/a/29613573/922013
ESCAPED_SECRET=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$secret_val")
# Iterate list of input path patterns and use grep to create a list of files that
# contain secrets
while IFS= read -r search_path || [[ -n $search_path ]];
do
[ $(grep -Rl $ESCAPED_SECRET $search_path 2>/dev/null >> __grep_search_output.txt) >= 0 ]
done < <(printf '%s' "${{ inputs.path }}")
if [ -s __grep_search_output.txt ]; then
uniq __grep_search_output.txt | xargs -I{} sed -i "s/$ESCAPED_SECRET/***$key***/g" {}
fi
rm -rf __grep_search_output.txt
fi
done
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}

0 comments on commit b235ae4

Please sign in to comment.