mirror of
https://github.com/imjasonh/another-checkov-action
synced 2026-07-07 00:22:57 +00:00
commit
392af04cb4
6 changed files with 142 additions and 116 deletions
4
.github/workflows/use-action.yml
vendored
4
.github/workflows/use-action.yml
vendored
|
|
@ -12,7 +12,7 @@ on:
|
|||
permissions: {}
|
||||
|
||||
jobs:
|
||||
checkov-strict:
|
||||
use-action:
|
||||
runs-on: ubuntu-latest
|
||||
name: Checkov Security Scan
|
||||
|
||||
|
|
@ -25,3 +25,5 @@ jobs:
|
|||
fetch-depth: 0 # Need full history for git diff
|
||||
|
||||
- uses: ./
|
||||
with:
|
||||
fail-on-error: false
|
||||
|
|
|
|||
62
README.md
62
README.md
|
|
@ -9,36 +9,13 @@ Run [Checkov](https://www.checkov.io/) security scans on your infrastructure cod
|
|||
```yaml
|
||||
- uses: imjasonh/another-checkov-action@...
|
||||
with:
|
||||
directory: '.' # Directory to scan (default: '.')
|
||||
framework: 'all' # Framework to scan: terraform, kubernetes, dockerfile, or all (default: 'all')
|
||||
fail-on-error: 'true' # Fail workflow on security issues (default: 'true')
|
||||
config-file: '' # Path to .checkov.yaml config (auto-detected if exists)
|
||||
# These are the defaults, you can run this without any flags.
|
||||
checkov-flags: '' # Flags to pass to checkov (e.g., '--directory . --framework terraform')
|
||||
fail-on-error: 'true' # Fail workflow on security issues (default: 'true')
|
||||
checkov-version: 'latest' # Checkov version to install (default: 'latest')
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```yaml
|
||||
name: Security Scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.tf'
|
||||
- '**.yaml'
|
||||
- '**/Dockerfile'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
checkov:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: imjasonh/another-checkov-action@...
|
||||
```
|
||||
By default, Checkov scans the current directory for all supported frameworks. Use `checkov-flags` to customize the scan (e.g., `--directory examples`, `--framework terraform`, `--skip-check CKV_AWS_1`, etc.).
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -54,14 +31,37 @@ See the [examples/](examples/) directory for intentionally misconfigured files t
|
|||
|
||||
## Configuration
|
||||
|
||||
Create a `.checkov.yaml` in your repository root to customize scanning:
|
||||
### Using checkov-flags
|
||||
|
||||
Pass any Checkov CLI flags via the `checkov-flags` input:
|
||||
|
||||
```yaml
|
||||
- uses: imjasonh/another-checkov-action@...
|
||||
with:
|
||||
checkov-flags: |
|
||||
--framework terraform
|
||||
--skip-check CKV_AWS_126 --compact
|
||||
```
|
||||
|
||||
### Using a config file
|
||||
|
||||
Create a `.checkov.yaml` in your repository root:
|
||||
|
||||
```yaml
|
||||
skip-check:
|
||||
- CKV_AWS_126 # Skip specific checks
|
||||
- CKV_AWS_126
|
||||
```
|
||||
|
||||
See [Checkov documentation](https://www.checkov.io/2.Basics/Suppressing%20and%20Skipping%20Policies.html) for more configuration options.
|
||||
Then reference it:
|
||||
|
||||
```yaml
|
||||
- uses: imjasonh/another-checkov-action@...
|
||||
with:
|
||||
checkov-flags: |
|
||||
--config-file .checkov.yaml
|
||||
```
|
||||
|
||||
See [Checkov documentation](https://www.checkov.io/2.Basics/CLI-Command-Reference.html) for all available flags.
|
||||
|
||||
## Limitations
|
||||
|
||||
|
|
|
|||
16
action.yml
16
action.yml
|
|
@ -6,14 +6,6 @@ branding:
|
|||
color: 'blue'
|
||||
|
||||
inputs:
|
||||
directory:
|
||||
description: 'Directory to scan (default: current directory)'
|
||||
required: false
|
||||
default: '.'
|
||||
framework:
|
||||
description: 'Filter scan to specific frameworks (e.g., terraform, kubernetes, dockerfile)'
|
||||
required: false
|
||||
default: 'all'
|
||||
fail-on-error:
|
||||
description: 'Fail the action if security issues are found'
|
||||
required: false
|
||||
|
|
@ -22,14 +14,10 @@ inputs:
|
|||
description: 'Version of checkov to install (default: latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
config-file:
|
||||
description: 'Path to Checkov config file (default: .checkov.yaml if it exists)'
|
||||
checkov-flags:
|
||||
description: 'Flags to pass to checkov (e.g., "--directory . --framework terraform --skip-check CKV_AWS_1")'
|
||||
required: false
|
||||
default: ''
|
||||
output-format:
|
||||
description: 'Output format for checkov (default: json)'
|
||||
required: false
|
||||
default: 'json'
|
||||
|
||||
outputs:
|
||||
results:
|
||||
|
|
|
|||
88
dist/index.js
vendored
88
dist/index.js
vendored
|
|
@ -29986,7 +29986,7 @@ async function addAnnotations(results, context) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if this file was changed in the PR
|
||||
// Only annotate files that were changed in the PR
|
||||
if (matchedPath) {
|
||||
const properties = {
|
||||
title: `${check.checkId}: ${check.checkName}`,
|
||||
|
|
@ -30006,8 +30006,6 @@ async function addAnnotations(results, context) {
|
|||
// Create error annotations for all failed checks
|
||||
core.error(message, properties);
|
||||
annotationCount++;
|
||||
} else {
|
||||
core.warning(`No match for file: ${check.file} (tried variations but none matched changed files)`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -32205,31 +32203,17 @@ const { addAnnotations } = __nccwpck_require__(5111);
|
|||
async function run() {
|
||||
try {
|
||||
// Get inputs
|
||||
const directory = core.getInput('directory') || '.';
|
||||
const framework = core.getInput('framework') || 'all';
|
||||
const failOnError = core.getInput('fail-on-error') !== 'false';
|
||||
const checkovVersion = core.getInput('checkov-version') || 'latest';
|
||||
let configFile = core.getInput('config-file') || '';
|
||||
|
||||
// Check for default config file if not specified
|
||||
if (!configFile) {
|
||||
const defaultConfigs = ['.checkov.yaml', '.checkov.yml'];
|
||||
for (const defaultConfig of defaultConfigs) {
|
||||
if (fs.existsSync(defaultConfig)) {
|
||||
configFile = defaultConfig;
|
||||
core.info(`Using config file: ${configFile}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const checkovFlags = core.getInput('checkov-flags') || '';
|
||||
|
||||
core.info('Installing Checkov...');
|
||||
await installCheckov(checkovVersion);
|
||||
|
||||
core.info(`Running Checkov scan on directory: ${directory}`);
|
||||
core.info('Running Checkov scan...');
|
||||
const outputFile = path.join(process.env.RUNNER_TEMP || '/tmp', 'checkov-results.json');
|
||||
|
||||
const exitCode = await runCheckov(directory, framework, outputFile, configFile);
|
||||
const exitCode = await runCheckov(outputFile, checkovFlags);
|
||||
|
||||
// Read and parse results
|
||||
let results = null;
|
||||
|
|
@ -32251,15 +32235,22 @@ async function run() {
|
|||
if (github.context.eventName === 'pull_request') {
|
||||
await addAnnotations(results, github.context);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle failure
|
||||
if (exitCode !== 0 && failOnError) {
|
||||
core.setFailed(`Checkov found security issues (exit code: ${exitCode})`);
|
||||
// Handle failure based on results (not exit code, since we use --soft-fail)
|
||||
if (results.summary.failed > 0 && failOnError) {
|
||||
core.setFailed(`Checkov found ${results.summary.failed} security issue${results.summary.failed > 1 ? 's' : ''}`);
|
||||
} else if (results.summary.failed > 0) {
|
||||
core.warning(`Checkov found ${results.summary.failed} security issue${results.summary.failed > 1 ? 's' : ''} but fail-on-error is disabled`);
|
||||
} else {
|
||||
core.info('Checkov scan completed successfully with no issues');
|
||||
}
|
||||
} else if (exitCode !== 0) {
|
||||
core.warning(`Checkov found security issues but fail-on-error is disabled`);
|
||||
} else {
|
||||
core.info('Checkov scan completed successfully with no issues');
|
||||
// Fallback if we couldn't parse results
|
||||
if (failOnError) {
|
||||
core.setFailed(`Checkov exited with code ${exitCode}`);
|
||||
} else {
|
||||
core.warning(`Checkov exited with code ${exitCode} but fail-on-error is disabled`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -32275,20 +32266,47 @@ async function installCheckov(version) {
|
|||
await exec.exec(pipCommand[0], pipCommand.slice(1));
|
||||
}
|
||||
|
||||
async function runCheckov(directory, framework, outputFile, configFile) {
|
||||
async function runCheckov(outputFile, checkovFlags) {
|
||||
let userFlags = [];
|
||||
|
||||
// Parse user flags if provided
|
||||
if (checkovFlags) {
|
||||
userFlags = checkovFlags.trim().split(/\s+/).filter(f => f);
|
||||
|
||||
// Check if user specified --output and warn/remove it
|
||||
const outputIndex = userFlags.findIndex(f => f === '--output' || f === '-o');
|
||||
if (outputIndex !== -1) {
|
||||
core.warning('Overriding --output flag (must be JSON for parsing)');
|
||||
userFlags.splice(outputIndex, 2); // Remove --output and its value
|
||||
}
|
||||
|
||||
// Check if user specified --soft-fail and remove it (we manage this)
|
||||
const softFailIndex = userFlags.findIndex(f => f === '--soft-fail');
|
||||
if (softFailIndex !== -1) {
|
||||
core.info('Removing redundant --soft-fail flag (already set by action)');
|
||||
userFlags.splice(softFailIndex, 1);
|
||||
}
|
||||
|
||||
if (userFlags.length > 0) {
|
||||
core.info(`Using checkov flags: ${userFlags.join(' ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add --directory . if not already specified
|
||||
const hasDirectory = userFlags.some(f => f.startsWith('--directory') || f === '-d');
|
||||
const hasFile = userFlags.some(f => f.startsWith('--file') || f === '-f');
|
||||
|
||||
const args = [
|
||||
'--directory', directory,
|
||||
'--output', 'json',
|
||||
'--soft-fail' // Always use soft-fail to capture results; we'll handle failure in the action
|
||||
'--soft-fail', // Always use soft-fail to capture results; we'll handle failure in the action
|
||||
];
|
||||
|
||||
if (framework !== 'all') {
|
||||
args.push('--framework', framework);
|
||||
// Default to current directory if neither --directory nor --file specified
|
||||
if (!hasDirectory && !hasFile) {
|
||||
args.push('--directory', '.');
|
||||
}
|
||||
|
||||
if (configFile) {
|
||||
args.push('--config-file', configFile);
|
||||
}
|
||||
args.push(...userFlags);
|
||||
|
||||
let exitCode = 0;
|
||||
let outputData = '';
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ async function addAnnotations(results, context) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if this file was changed in the PR
|
||||
// Only annotate files that were changed in the PR
|
||||
if (matchedPath) {
|
||||
const properties = {
|
||||
title: `${check.checkId}: ${check.checkName}`,
|
||||
|
|
@ -79,8 +79,6 @@ async function addAnnotations(results, context) {
|
|||
// Create error annotations for all failed checks
|
||||
core.error(message, properties);
|
||||
annotationCount++;
|
||||
} else {
|
||||
core.warning(`No match for file: ${check.file} (tried variations but none matched changed files)`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
84
src/index.js
84
src/index.js
|
|
@ -10,31 +10,17 @@ const { addAnnotations } = require('./annotations');
|
|||
async function run() {
|
||||
try {
|
||||
// Get inputs
|
||||
const directory = core.getInput('directory') || '.';
|
||||
const framework = core.getInput('framework') || 'all';
|
||||
const failOnError = core.getInput('fail-on-error') !== 'false';
|
||||
const checkovVersion = core.getInput('checkov-version') || 'latest';
|
||||
let configFile = core.getInput('config-file') || '';
|
||||
|
||||
// Check for default config file if not specified
|
||||
if (!configFile) {
|
||||
const defaultConfigs = ['.checkov.yaml', '.checkov.yml'];
|
||||
for (const defaultConfig of defaultConfigs) {
|
||||
if (fs.existsSync(defaultConfig)) {
|
||||
configFile = defaultConfig;
|
||||
core.info(`Using config file: ${configFile}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const checkovFlags = core.getInput('checkov-flags') || '';
|
||||
|
||||
core.info('Installing Checkov...');
|
||||
await installCheckov(checkovVersion);
|
||||
|
||||
core.info(`Running Checkov scan on directory: ${directory}`);
|
||||
core.info('Running Checkov scan...');
|
||||
const outputFile = path.join(process.env.RUNNER_TEMP || '/tmp', 'checkov-results.json');
|
||||
|
||||
const exitCode = await runCheckov(directory, framework, outputFile, configFile);
|
||||
const exitCode = await runCheckov(outputFile, checkovFlags);
|
||||
|
||||
// Read and parse results
|
||||
let results = null;
|
||||
|
|
@ -56,15 +42,22 @@ async function run() {
|
|||
if (github.context.eventName === 'pull_request') {
|
||||
await addAnnotations(results, github.context);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle failure
|
||||
if (exitCode !== 0 && failOnError) {
|
||||
core.setFailed(`Checkov found security issues (exit code: ${exitCode})`);
|
||||
// Handle failure based on results (not exit code, since we use --soft-fail)
|
||||
if (results.summary.failed > 0 && failOnError) {
|
||||
core.setFailed(`Checkov found ${results.summary.failed} security issue${results.summary.failed > 1 ? 's' : ''}`);
|
||||
} else if (results.summary.failed > 0) {
|
||||
core.warning(`Checkov found ${results.summary.failed} security issue${results.summary.failed > 1 ? 's' : ''} but fail-on-error is disabled`);
|
||||
} else {
|
||||
core.info('Checkov scan completed successfully with no issues');
|
||||
}
|
||||
} else if (exitCode !== 0) {
|
||||
core.warning(`Checkov found security issues but fail-on-error is disabled`);
|
||||
} else {
|
||||
core.info('Checkov scan completed successfully with no issues');
|
||||
// Fallback if we couldn't parse results
|
||||
if (failOnError) {
|
||||
core.setFailed(`Checkov exited with code ${exitCode}`);
|
||||
} else {
|
||||
core.warning(`Checkov exited with code ${exitCode} but fail-on-error is disabled`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -80,20 +73,47 @@ async function installCheckov(version) {
|
|||
await exec.exec(pipCommand[0], pipCommand.slice(1));
|
||||
}
|
||||
|
||||
async function runCheckov(directory, framework, outputFile, configFile) {
|
||||
async function runCheckov(outputFile, checkovFlags) {
|
||||
let userFlags = [];
|
||||
|
||||
// Parse user flags if provided
|
||||
if (checkovFlags) {
|
||||
userFlags = checkovFlags.trim().split(/\s+/).filter(f => f);
|
||||
|
||||
// Check if user specified --output and warn/remove it
|
||||
const outputIndex = userFlags.findIndex(f => f === '--output' || f === '-o');
|
||||
if (outputIndex !== -1) {
|
||||
core.warning('Overriding --output flag (must be JSON for parsing)');
|
||||
userFlags.splice(outputIndex, 2); // Remove --output and its value
|
||||
}
|
||||
|
||||
// Check if user specified --soft-fail and remove it (we manage this)
|
||||
const softFailIndex = userFlags.findIndex(f => f === '--soft-fail');
|
||||
if (softFailIndex !== -1) {
|
||||
core.info('Removing redundant --soft-fail flag (already set by action)');
|
||||
userFlags.splice(softFailIndex, 1);
|
||||
}
|
||||
|
||||
if (userFlags.length > 0) {
|
||||
core.info(`Using checkov flags: ${userFlags.join(' ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add --directory . if not already specified
|
||||
const hasDirectory = userFlags.some(f => f.startsWith('--directory') || f === '-d');
|
||||
const hasFile = userFlags.some(f => f.startsWith('--file') || f === '-f');
|
||||
|
||||
const args = [
|
||||
'--directory', directory,
|
||||
'--output', 'json',
|
||||
'--soft-fail' // Always use soft-fail to capture results; we'll handle failure in the action
|
||||
'--soft-fail', // Always use soft-fail to capture results; we'll handle failure in the action
|
||||
];
|
||||
|
||||
if (framework !== 'all') {
|
||||
args.push('--framework', framework);
|
||||
// Default to current directory if neither --directory nor --file specified
|
||||
if (!hasDirectory && !hasFile) {
|
||||
args.push('--directory', '.');
|
||||
}
|
||||
|
||||
if (configFile) {
|
||||
args.push('--config-file', configFile);
|
||||
}
|
||||
args.push(...userFlags);
|
||||
|
||||
let exitCode = 0;
|
||||
let outputData = '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue