-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
yaron-cider
committed
Aug 3, 2024
0 parents
commit 9078f6b
Showing
8 changed files
with
2,415 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Palo Alto Networks. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<img src="logo-artifact.jpeg"> | ||
|
||
|
||
# Upload secure artifact | ||
|
||
This GitHub action scans artifacts for secrets using [gitleaks](https://github.com/gitleaks/gitleaks) before uploading them. The upload functionality is performed using the [@actions/artifact](https://www.npmjs.com/package/@actions/artifact) package, which is also used by the [upload-artifact](https://github.com/actions/upload-artifact) GitHub action. The purpose of this action is to ensure that no secrets are included in the uploaded artifacts. | ||
<br> | ||
<br> | ||
<img src="oss.png"> | ||
|
||
## Features | ||
|
||
- Compatible with upload-artifact v4 | ||
- Alerts users if any secrets are detected | ||
- Prevents uploading artifacts if secrets are found | ||
- Wraps the `upload-artifact` GitHub action for seamless integration | ||
|
||
## Usage | ||
|
||
To use the action, add it to your GitHub Actions workflow file: | ||
|
||
```yaml | ||
|
||
- uses: PaloAltoNetworks/upload-secure-artifact@main | ||
with: | ||
name: python-build | ||
path: /output | ||
|
||
``` | ||
|
||
** _Pin your actions for a safer world_ | ||
|
||
## Inputs | ||
|
||
- original upload-artifact inputs can be found [here](https://github.com/actions/upload-artifact?tab=readme-ov-file#inputs) | ||
|
||
- scan-only-runner-token (Optional) | ||
|
||
- Description: If true, skip the gitleaks secrets scanning and only perform scanning for the runner token (GITHUB_TOKEN) in the local .git folder. | ||
- Default: false | ||
|
||
## Outputs | ||
|
||
- artifact-id, artifact-url: supplied by the upload-artifact action | ||
|
||
|
||
## Contributing | ||
|
||
Contributions are welcome! Please open an issue or submit a pull request if you have any improvements or suggestions. | ||
|
||
## Contact | ||
|
||
For any questions or support, please open an issue on the GitHub repository. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
name: upload-secure-artifact | ||
description: A composite action that runs gitleaks for scanning secrets and then executes an index.js file. | ||
inputs: | ||
name: | ||
description: 'Artifact name' | ||
default: 'artifact' | ||
path: | ||
description: 'A file, directory or wildcard pattern that describes what to upload' | ||
required: true | ||
scan-only-runner-token: | ||
description: > | ||
If true don't use GitLeaks to scan secrets | ||
default: 'false' | ||
retention-days: | ||
description: > | ||
Duration after which artifact will expire in days. 0 means using default retention. | ||
Minimum 1 day. | ||
Maximum 90 days unless changed from the repository settings page. | ||
compression-level: | ||
description: > | ||
The level of compression for Zlib to be applied to the artifact archive. | ||
The value can range from 0 to 9: | ||
- 0: No compression | ||
- 1: Best speed | ||
- 6: Default compression (same as GNU Gzip) | ||
- 9: Best compression | ||
Higher levels will result in better compression, but will take longer to complete. | ||
For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads. | ||
default: '6' | ||
|
||
outputs: | ||
artifact-id: | ||
description: > | ||
A unique identifier for the artifact that was just uploaded. Empty if the artifact upload failed. | ||
This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts | ||
artifact-url: | ||
description: > | ||
A download URL for the artifact that was just uploaded. Empty if the artifact upload failed. | ||
This download URL only works for requests Authenticated with GitHub. Anonymous downloads will be prompted to first login. | ||
If an anonymous download URL is needed than a short time restricted URL can be generated using the download artifact API: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact | ||
This URL will be valid for as long as the artifact exists and the workflow run and repository exists. Once an artifact has expired this URL will no longer work. | ||
Common uses cases for such a download URL can be adding download links to artifacts in descriptions or comments on pull requests or issues. | ||
runs: | ||
using: "composite" | ||
steps: | ||
|
||
- name: Secrets Scanning | ||
if: inputs.scan-only-runner-token == 'false' | ||
run: docker run -v /$(pwd)/${{ inputs.path }}:/scan zricethezav/gitleaks@sha256:75bdb2b2f4db213cde0b8295f13a88d6b333091bbfbf3012a4e083d00d31caba detect --no-git --source /scan | ||
shell: bash | ||
|
||
- name: Set up Node.js | ||
uses: actions/setup-node@26961cf329f22f6837d5f54c3efd76b480300ace #3.03 | ||
with: | ||
node-version: 20 | ||
|
||
- name: npm install | ||
shell: bash | ||
run: | | ||
cd ${{ github.action_path }} | ||
npm ci | ||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #7.01 | ||
with: | ||
script: | | ||
const script = require('${{github.action_path}}/index.js') | ||
script({"github":github, "context":context,artifactName:"${{ inputs.name }}",artifactPath:"${{ inputs.path }}",retentionDays:"${{ inputs.retention-days }}",compressionLevel:"${{ inputs.compression-level }}"}) | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
const { DefaultArtifactClient } = require("@actions/artifact"); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const core = require('@actions/core'); | ||
|
||
async function main(github, context, artifactName,artifactPath,retentionDays,compressionLevel) { | ||
const artifactClient = new DefaultArtifactClient(); | ||
|
||
try { | ||
await uploadArtifact(artifactClient, artifactName, artifactPath,retentionDays,compressionLevel); | ||
} catch (error) { | ||
core.setFailed(error.message); | ||
} | ||
} | ||
|
||
async function uploadArtifact(artifactClient, artifactName, artifactPath,retentionDays,compressionLevel) { | ||
foundPath = hasGitFolderWithGitHubRunnerToken(artifactPath) | ||
if (foundPath) { | ||
throw new Error(`Found GITHUB_TOKEN in artifact, under path ${foundPath}`); | ||
} | ||
|
||
const filesToUpload = await populateFilesWithFullPath(artifactPath); | ||
|
||
await artifactClient.uploadArtifact( | ||
artifactName, | ||
filesToUpload, | ||
process.env.GITHUB_WORKSPACE, | ||
{ retentionDays: 10 } // Optional: Set retention days | ||
); | ||
} | ||
|
||
|
||
function findGitFolder(startPath) { | ||
if (!fs.existsSync(startPath)) { | ||
console.log("Start path does not exist."); | ||
return null; | ||
} | ||
|
||
const files = fs.readdirSync(startPath); | ||
|
||
for (let i = 0; i < files.length; i++) { | ||
const filePath = path.join(startPath, files[i]); | ||
|
||
if (files[i] === '.git' && fs.statSync(filePath).isDirectory()) { | ||
return filePath; | ||
} | ||
|
||
if (fs.statSync(filePath).isDirectory()) { | ||
const result = findGitFolder(filePath); | ||
if (result) { | ||
return result; | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
function hasGitFolderWithGitHubRunnerToken(pathToCheck) { | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const gitDir = findGitFolder(pathToCheck, '.git'); | ||
const configFile = path.join(gitDir, 'config'); | ||
const regex = new RegExp('eC1hY2Nlc3MtdG9rZW46Z2hz', 'i'); | ||
|
||
try { | ||
if (fs.existsSync(gitDir) && fs.existsSync(configFile)) { | ||
const configContent = fs.readFileSync(configFile, 'utf-8'); | ||
if (regex.test(configContent)) { | ||
return configFile; | ||
} | ||
} | ||
} catch (error) { | ||
console.error('Error checking Git config:', error); | ||
return null; | ||
} | ||
} | ||
|
||
async function populateFilesWithFullPath(rootPath) { | ||
const fs = require('fs').promises; // Use promises for cleaner async/await usage | ||
const path = require('path'); | ||
const files = []; | ||
|
||
const dirEntries = await fs.readdir(rootPath); | ||
for (const fileName of dirEntries) { | ||
const filePath = path.join(rootPath, fileName); | ||
|
||
const stats = await fs.stat(filePath); | ||
if (stats.isFile()) { | ||
files.push(filePath); | ||
} else if (stats.isDirectory()) { | ||
// Recursively collect files from subdirectories | ||
files.push(...(await populateFilesWithFullPath(filePath))); | ||
} | ||
} | ||
|
||
return files; | ||
} | ||
|
||
module.exports = function ({ github, context , artifactName,artifactPath,retentionDays,compressionLevel }) { | ||
main(github, context, artifactName,artifactPath,retentionDays,compressionLevel); | ||
} | ||
|
||
|
||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.