Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
yaron-cider committed Aug 3, 2024
0 parents commit 9078f6b
Show file tree
Hide file tree
Showing 8 changed files with 2,415 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
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.
53 changes: 53 additions & 0 deletions README.md
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.
74 changes: 74 additions & 0 deletions action.yaml
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 }}"})
106 changes: 106 additions & 0 deletions index.js
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);
}



Binary file added logo-artifact.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added oss.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9078f6b

Please sign in to comment.