diff --git a/.github/configs/commitlint.config.js b/.github/configs/commitlint.config.js new file mode 100644 index 0000000..2c3951d --- /dev/null +++ b/.github/configs/commitlint.config.js @@ -0,0 +1,27 @@ +const { maxLineLength } = require('@commitlint/ensure') + +const bodyMaxLineLength = 100 + +const validateBodyMaxLengthIgnoringDeps = (parsedCommit) => { + const { type, scope, body } = parsedCommit + const isDepsCommit = + type === 'chore' && scope === 'release' + + return [ + isDepsCommit || !body || maxLineLength(body, bodyMaxLineLength), + `body's lines must not be longer than ${bodyMaxLineLength}`, + ] +} + +module.exports = { + extends: ['@commitlint/config-conventional'], + plugins: ['commitlint-plugin-function-rules'], + rules: { + 'body-max-line-length': [0], + 'function-rules/body-max-line-length': [ + 2, + 'always', + validateBodyMaxLengthIgnoringDeps, + ], + }, +} diff --git a/.github/workflows/checks.base.yaml b/.github/workflows/checks.base.yaml new file mode 100644 index 0000000..015fc18 --- /dev/null +++ b/.github/workflows/checks.base.yaml @@ -0,0 +1,16 @@ +name: Checks (base) + +on: + workflow_call: + +jobs: + commitlint: + name: (check) Commitlint + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Run Commitlint + uses: wagoid/commitlint-github-action@v5 + with: + configFile: .github/configs/commitlint.config.js diff --git a/.github/workflows/checks.branches.yaml b/.github/workflows/checks.branches.yaml new file mode 100644 index 0000000..f22bfbf --- /dev/null +++ b/.github/workflows/checks.branches.yaml @@ -0,0 +1,11 @@ +name: Checks (branches) + +on: + push: + branches-ignore: + - main + - staging + +jobs: + checks: + uses: ./.github/workflows/checks.base.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..00961b4 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,46 @@ +name: Release +run-name: "Release `${{ github.ref_name }}` (SHA: ${{ github.sha }})" + +on: + push: + branches: + - main + - staging + +permissions: + contents: read + +jobs: + checks: + name: Run Checks + uses: ./.github/workflows/checks.base.yaml + release: + name: Release + needs: checks + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Install plugins + run: > + npm install -D + @semantic-release/git + @semantic-release/changelog + conventional-changelog-conventionalcommits + @saithodev/semantic-release-backmerge + - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies + run: npm audit signatures + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47fb503 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +# Dependency directory +node_modules + +# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built ts files +__tests__/runner/* + +# IDE files +.idea +.vscode +*.code-workspace diff --git a/.releaserc.yaml b/.releaserc.yaml new file mode 100644 index 0000000..cb54f1d --- /dev/null +++ b/.releaserc.yaml @@ -0,0 +1,129 @@ +preset: conventionalcommits +tagFormat: "${version}" + +branches: + - "+([0-9])?(.{+([0-9]),x}).x" + - main + - next + - next-major + - name: staging + prerelease: rc + - name: beta + prerelease: true + - name: alpha + prerelease: true + +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - "@semantic-release/changelog" + - "@semantic-release/git" + - "@semantic-release/github" + - "@saithodev/semantic-release-backmerge" + +verifyConditions: + - "@semantic-release/changelog" + - "@semantic-release/git" + - "@semantic-release/github" + - path: "@saithodev/semantic-release-backmerge" + backmergeBranches: + - from: main + to: staging + +analyzeCommits: + - path: "@semantic-release/commit-analyzer" + releaseRules: + - breaking: true + release: major + - type: build + release: patch + - type: chore + release: false + - type: ci + release: false + - type: docs + release: patch + - type: feat + release: minor + - type: fix + release: patch + - type: perf + release: patch + - type: refactor + release: false + - type: revert + release: patch + - type: style + release: false + - type: test + release: false + +generateNotes: + - path: "@semantic-release/release-notes-generator" + writerOpts: + groupBy: type + commitGroupsSort: title + commitsSort: header + linkCompare: true + linkReferences: true + presetConfig: + types: + - type: build + section: "๐ŸฆŠ CI/CD" + hidden: false + - type: chore + section: "Other" + hidden: true + - type: ci + section: "๐ŸฆŠ CI/CD" + hidden: false + - type: docs + section: "๐Ÿ“” Docs" + hidden: false + - type: example + section: "๐Ÿ“ Examples" + hidden: true + - type: feat + section: "๐Ÿš€ Features" + hidden: false + - type: fix + section: "๐Ÿ›  Fixes" + hidden: false + - type: perf + section: "โฉ Performance" + hidden: false + - type: refactor + section: ":scissors: Refactor" + hidden: false + - type: revert + section: "๐Ÿ™…โ€โ™‚๏ธ Reverts" + hidden: false + - type: style + section: "๐Ÿ’ˆ Style" + hidden: false + - type: test + section: "๐Ÿงช Tests" + hidden: false + +prepare: + - path: "@semantic-release/changelog" + changelogFile: CHANGELOG.md + + - path: "@semantic-release/git" + message: "chore(release): release <%= nextRelease.version %> - <%= new Date().toLocaleDateString('en-US', {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' }) %> \n\n<%= nextRelease.notes %>" + assets: + - CHANGELOG.md + - pyproject.toml + +publish: + - path: "@semantic-release/github" + +success: + - path: "@semantic-release/github" + - path: "@saithodev/semantic-release-backmerge" + backmergeBranches: + - from: main + to: staging + +fail: + - path: "@semantic-release/github" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3cd08a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ethiack, Lda. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6398e44 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ + + +[version-shield]: https://img.shields.io/github/v/release/ethiack/github-action?style=for-the-badge +[version-url]: https://github.com/ethiack/github-action/releases/latest + +[license-shield]: https://img.shields.io/github/license/ethiack/github-action?style=for-the-badge +[license-url]: LICENSE + +[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 +[linkedin-url]: https://linkedin.com/company/ethiack + + + + +
+ +

+
+ logo +

+ Ethiack GitHub Action +

+

+ +

GitHub Action for integrating Ethiack's Public API in GitHub Workflows.

+ +[![GitHub Release][version-shield]][version-url] +[![MIT License][license-shield]][license-url] +[![LinkedIn][linkedin-shield]][linkedin-url] + +
+ + +[Introduction](#introduction) โ€ข +[Credentials Setup](#credentials-setup) โ€ข +[Usage](#usage) โ€ข +[License](#license) + + +
+ +## Introduction + +This GitHub Action facilitates the integration of [Ethiack's Public API](https://api.ethiack.com) ([API docs](https://portal.ethiack.com/docs/api/)) for launching scans through GitHub workflows. By utilizing this action, you can seamlessly incorporate Ethiack's security scanning capabilities into your CI/CD pipeline, enhancing your software development lifecycle with automated security testing. + +

(back to top)

+ +## Credentials Setup + + +Using Ethiack's API - and, therefore, this Action - requires authentication using an *API Key* and *API Secret*, which can be retrieved in [Ethiack's Portal settings page](https://portal.ethiack.com/settings/api). These credentials must be available as environment variables `ETHIACK_API_KEY` and `ETHIACK_API_SECRET`, repectively, whenever this action is used. + +To configure, navigate to your repository settings, select `Secrets and variables`, then `Actions`, and add the following secrets: + +- `ETHIACK_API_KEY`: Your API Key +- `ETHIACK_API_SECRET`: Your API Secret + +> [!CAUTION] +> Ensure that these secrets are referenced in your workflow files securely. + + +

(back to top)

+ +## Usage + + +> [!NOTE] +> This GitHub action is fundamentally a wrapper around [Ethiack's Public API](https://api.ethiack.com/), using [Ethiack's Job Manager Package](https://github.com/ethiack/job-manager). For more information, see the [API docs](https://portal.ethiack.com/docs/api/) and refer to the later package. + + +### **Example:** *Launching a job and waiting for its conclusion* + +This pipeline launches a scan for the domain `https://example.ethiack.com` and waits until it finishes (cf. `--wait` flag). If vulnerabilities with severity `medium` or higher are found, the success of the job is interpreted as failing, and this pipeline step will exit with a non-zero status code (cf. `--fail` flag). + +```yaml +jobs: + ethiack-scan: + runs-on: ubuntu-latest + steps: + - name: Launch Ethiack Scan + uses: ethiack/github-action@main + with: + command: launch + url: https://example.ethiack.com + args: --wait --fail --severity medium + env: + ETHIACK_API_SECRET: ${{ secrets.ETHIACK_API_SECRET }} + ETHIACK_API_KEY: ${{ secrets.ETHIACK_API_KEY }} +``` + + +### **Example:** *Checking the success of a job.* + +This pipeline checks the success of a job. It will fail if the respective job has finished and vulnerabilities with severity equal or above `high` were found. + + +```yaml +jobs: + ethiack-check-job-success: + runs-on: ubuntu-latest + steps: + - name: Check Ethiack Job Success + uses: ethiack/github-action@main + with: + command: success + uuid: 'your-job-uuid' + args: --severity high --fail + env: + ETHIACK_API_SECRET: ${{ secrets.ETHIACK_API_SECRET }} + ETHIACK_API_KEY: ${{ secrets.ETHIACK_API_KEY }} +``` + +> [!NOTE] +> For retrieving the success of a job without exiting the pipeline, simply provide the flag `--no-fail` instead of `--fail` in the previous example. + + +### Available commands + +This GitHub Action supports every command provided by [Ethiack's Job Manager Package](https://github.com/ethiack/job-manager). This includes, but is not necessarily limited to, the commands: + +
+ +| Command | Description | Required Inputs | +|:---------: |:----------------------------------------------------: |:---------------: | +| `check` | Check if a URL is valid and a job can be submitted | `url` | +| `launch` | Launch a job and, optionally, wait for it to finish. | `url` | +| `info` | Retrieve information about a job. | `uuid` | +| `list` | List all jobs for the organization. | - | +| `status` | Retrieve the status of a job. | `uuid` | +| `success` | Retrieve the success of a job. | `uuid` | +| `await` | Wait for a job to finish. | `uuid` | +| `cancel` | Cancel a queued or running job. | `uuid` | + +
+ + +#### Required Inputs + +> The `url` input refers to the target Uniform Resource Locator (URL) of the service for which the command is run. + +> The `uuid` input refers to the Universal Unique Identifier (UUID) of the job for which the command is run. + +#### Optional Arguments +> The behaviour of these commands can be customized with flags and additional parameters provided inn the `args:` variable in the workflow step (e.g., the `--fail` and `--severity` flags in the examples above). For more information regarding the available options and flags for each command, please refer to the [Job Manager Package](https://github.com/ethiack/job-manager). + +

(back to top)

+ + +## License +Distributed under the MIT License. See [LICENSE](LICENSE) for more information. + +

(back to top)

diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..00d6389 --- /dev/null +++ b/action.yaml @@ -0,0 +1,96 @@ +name: 'Ethiack Job Manager Action' +description: 'Integrates Ethiack Job Manager with GitHub Actions for managing Artiacker jobs.' +author: 'Ethiack, Lda.' + +branding: + icon: 'search' + color: 'green' + +inputs: + command: + description: 'The command to run. Available options are "launch", "cancel", "info", "list", "status", "success", "await", and "check".' + required: true + uuid: + description: 'The UUID of the job. Required for commands "cancel", "info", "status", "success", and "await".' + required: false + default: '' + url: + description: 'The URL of the target service. Required for commands "launch", and "check".' + required: false + default: '' + args: + description: 'The arguments to pass to the job.' + required: false + default: '' + +outputs: + response: + description: 'The response from the Ethiack Job Manager package.' + value: ${{ steps.ethiack-run-command.outputs.response }} + +runs: + using: 'composite' + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Setup environment + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --user --upgrade pipx + echo "PATH=$PATH:/root/.local/bin" >> $GITHUB_ENV + pipx install ethiack-job-manager + shell: bash + + - name: Execute Ethiack Job Manager Command + id: ethiack-run-command + run: | + # Run ethiack-job-manager + + set +e # Disable exit on error + + # Prepare command + COMMAND="ethiack-job-manager ${{ inputs.command }}" + + ## Add args + if [ -n "${{ inputs.args }}" ]; then + COMMAND="$COMMAND ${{ inputs.args }}" + fi + + ## Add url input if needed + if [ "${{ inputs.command }}" = "launch" ] || [ "${{ inputs.command }}" = "check" ]; then + if [ -n "${{ inputs.url }}" ]; then + COMMAND="$COMMAND ${{ inputs.url }}" + else + echo "Error: URL is required for the ${{ inputs.command }} command." + exit 1 + fi + fi + + ## Add uuid input if needed + if [ "${{ inputs.command }}" = "cancel" ] || [ "${{ inputs.command }}" = "info" ] || [ "${{ inputs.command }}" = "status" ] || [ "${{ inputs.command }}" = "success" ] || [ "${{ inputs.command }}" = "await" ]; then + if [ -n "${{ inputs.uuid }}" ]; then + COMMAND="$COMMAND ${{ inputs.uuid }}" + else + echo "Error: UUID is required for the ${{ inputs.command }} command." + exit 1 + fi + fi + + # Run + echo "[ETHIACK-JOB-MANAGER] Executing command: $COMMAND" + RESPONSE=$($COMMAND) + EXIT_CODE=$? + echo "[ETHIACK-JOB-MANAGER] Response:" + echo "$RESPONSE" + echo "response<> $GITHUB_OUTPUT + echo "[ETHIACK-JOB-MANAGER] Finished." + + set -e # Re-enable immediate exit on error + if [ $EXIT_CODE -ne 0 ]; then + echo "[ETHIACK-JOB-MANAGER] Command failed with exit code $EXIT_CODE." + exit $EXIT_CODE + fi + shell: bash diff --git a/assets/logo.webp b/assets/logo.webp new file mode 100644 index 0000000..791bb6d Binary files /dev/null and b/assets/logo.webp differ