Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(action): add new options and handle large files #27

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ inputs:
description: 'Scan for only system events'
required: false
default: 'false'
output:
description: 'Output path for the files to be placed'
required: false
default: './knoxctl-results'
kubearmor_version:
description: 'KubeArmor version to install (default: latest)'
required: false
Expand All @@ -28,6 +24,24 @@ inputs:
description: 'Policy action (Audit or Block)'
required: false
default: 'Audit'
dryrun:
description: 'Generate hardening security policies, but do not apply them'
required: false
default: 'false'
strict:
description: 'Apply all the hardening policies (this might generate a lot of alerts)'
required: false
default: 'false'
policies:
description: 'Path to user defined policies'
required: false
ignore-alerts:
description: 'Ignore alerts (file, network or process)'
required: false
min-severity:
description: 'Set the minimum severity level (1-10)'
required: false


runs:
using: 'node20'
Expand Down
48 changes: 37 additions & 11 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ async function runKnoxctlScan(): Promise<void> {
{ name: "all", flag: "--all", type: "boolean" },
{ name: "system", flag: "--system", type: "boolean" },
{ name: "output", flag: "--output", type: "string" },
{ name: "ignore-alerts", flag: "--ignore-alerts", type: "string" },
{ name: "min-severity", flag: "--min-severity", type: "string" },
];

let policyAction = core.getInput("policy_action").toLowerCase();
Expand All @@ -134,17 +136,37 @@ async function runKnoxctlScan(): Promise<void> {
// Capitalize the first letter
policyAction = policyAction.charAt(0).toUpperCase() + policyAction.slice(1);

// Run the policy command first
await exec.exec("knoxctl", [
// Prepare policy command options
const policyCommand = [
"knoxctl",
"scan",
"policy",
"--event",
"ADDED",
"--action",
policyAction,
]);
];

// Add new policy options
const dryrun = core.getBooleanInput("dryrun");
if (dryrun) {
policyCommand.push("--dryrun");
}

const strict = core.getBooleanInput("strict");
if (strict) {
policyCommand.push("--strict");
}

const command: string[] = ["knoxctl", "scan"];
const policies = core.getInput("policies");
if (policies) {
policyCommand.push("--policies", policies);
}

// Run the policy command first
await exec.exec(policyCommand[0], policyCommand.slice(1));

const scanCommand: string[] = ["knoxctl", "scan"];
let outputDir = getOutputDir();

for (const option of knoxctlOptions) {
Expand All @@ -153,15 +175,15 @@ async function runKnoxctlScan(): Promise<void> {
if (option.type === "boolean") {
value = core.getBooleanInput(option.name);
if (value) {
command.push(option.flag);
scanCommand.push(option.flag);
}
} else if (option.type === "string") {
value = core.getInput(option.name);
if (value) {
if (option.name === "output") {
outputDir = value;
}
command.push(option.flag, value);
scanCommand.push(option.flag, value);
}
}
}
Expand All @@ -174,13 +196,17 @@ async function runKnoxctlScan(): Promise<void> {
log(`Output directory already exists: ${outputDir}`);
}

const commandString = command.join(" ");
const commandString = scanCommand.join(" ");
log(`Executing command: ${commandString}`);

const scanProcess: ChildProcess = spawn(command[0], command.slice(1), {
stdio: "inherit",
detached: true,
});
const scanProcess: ChildProcess = spawn(
scanCommand[0],
scanCommand.slice(1),
{
stdio: "inherit",
detached: true,
},
);

log(`knoxctl scan started with PID: ${scanProcess.pid}`);

Expand Down
116 changes: 98 additions & 18 deletions src/post/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,86 @@ const NETWORK_EVENTS_PREFIX = "knoxctl_scan_network_events_md_";
const PROCESS_TREE_PREFIX = "knoxctl_scan_process_tree_";
const ALERTS_PREFIX = "knoxctl_scan_processed_alerts_";

const MAX_SUMMARY_SIZE = 1024 * 1024;
const TRUNCATION_MESSAGE =
"\n\n... (content truncated due to size limits, please download artifacts) ...";

function truncateContent(content: string, maxSize: number): string {
if (Buffer.byteLength(content, "utf8") <= maxSize) {
return content;
}

let truncated = content;
while (Buffer.byteLength(truncated + TRUNCATION_MESSAGE, "utf8") > maxSize) {
truncated = truncated.slice(0, -100);
}

return truncated + TRUNCATION_MESSAGE;
}

async function uploadContentAsArtifact(
content: string,
fileName: string,
): Promise<string> {
const artifactClient = artifact.create();
const artifactName = `full-content-${fileName}`;
const tempDir = path.join(getOutputDir(), "temp-artifacts");
const tempFile = path.join(tempDir, fileName);

if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}

fs.writeFileSync(tempFile, content, ENCODING);

try {
const uploadResult = await artifactClient.uploadArtifact(
artifactName,
[tempFile],
tempDir,
{ continueOnError: false },
);

log(`Uploaded full content as artifact: ${uploadResult.artifactName}`);
return artifactName;
} catch (error) {
log(
`Failed to upload content as artifact: ${error instanceof Error ? error.message : String(error)}`,
"error",
);
return "";
} finally {
fs.unlinkSync(tempFile);
}
}

async function addToSummaryWithSizeCheck(
content: string,
title: string,
): Promise<void> {
let summaryContent = `## ${title}\n\n${content}`;
const contentSize = Buffer.byteLength(summaryContent, "utf8");

if (contentSize > MAX_SUMMARY_SIZE) {
log(
`Content for ${title} exceeds maximum size. Truncating and uploading full content as artifact.`,
);
const artifactName = await uploadContentAsArtifact(
content,
`${title.toLowerCase().replace(/\s+/g, "-")}.md`,
);

summaryContent = truncateContent(summaryContent, MAX_SUMMARY_SIZE - 200);
summaryContent += `\n\n[View full content](${artifactName})`;
}

if (IS_GITHUB_ACTIONS) {
core.summary.addRaw(summaryContent).addEOL();
} else {
console.log(summaryContent);
}
}

function stopKnoxctlScan(): void {
const pidFile = getPidFilePath();
if (fs.existsSync(pidFile)) {
Expand Down Expand Up @@ -69,34 +149,26 @@ function getLatestFile(directory: string, prefix: string): string | null {
return matchingFiles.length > 0 ? matchingFiles[0].name : null;
}

function addToSummary(content: string): void {
if (IS_GITHUB_ACTIONS) {
core.summary.addRaw(content).addEOL();
} else {
console.log(content);
}
}

function processResultFile(
async function processResultFile(
outputDir: string,
prefix: string,
title: string,
emoji: string,
): void {
): Promise<void> {
const file = getLatestFile(outputDir, prefix);
if (file) {
const filePath = path.join(outputDir, file);
log(`Processing ${title} file: ${filePath}`);
const content = fs.readFileSync(filePath, ENCODING);
addToSummary(`## ${emoji} ${title}\n\n${content}`);
await addToSummaryWithSizeCheck(`${emoji} ${title}\n\n${content}`, title);
} else {
log(
`No ${title.toLowerCase()} file found with prefix ${prefix} in ${outputDir}`,
);
}
}

function processResults(): void {
async function processResults(): Promise<void> {
const outputDir = path.resolve(getOutputDir());
log(`Processing knoxctl results from directory: ${outputDir}`);

Expand All @@ -112,11 +184,19 @@ function processResults(): void {
);
}

addToSummary("# 📊 Runtime Security Report Generated by AccuKnox\n\n");
await addToSummaryWithSizeCheck(
"📊 Runtime Security Report Generated by AccuKnox",
"Report Overview",
);

processResultFile(outputDir, NETWORK_EVENTS_PREFIX, "Network Events", "🌐");
processResultFile(outputDir, PROCESS_TREE_PREFIX, "Process Tree", "🖥️");
processResultFile(outputDir, ALERTS_PREFIX, "Alerts Summary", "🚨");
await processResultFile(outputDir, ALERTS_PREFIX, "Alerts Summary", "🚨");
await processResultFile(outputDir, PROCESS_TREE_PREFIX, "Process Tree", "🖥️");
await processResultFile(
outputDir,
NETWORK_EVENTS_PREFIX,
"Network Events",
"🌐",
);

if (!IS_GITHUB_ACTIONS) {
log(
Expand All @@ -128,8 +208,8 @@ function processResults(): void {
async function run(): Promise<void> {
try {
stopKnoxctlScan();
await new Promise((resolve) => setTimeout(resolve, 6000));
processResults();
await new Promise((resolve) => setTimeout(resolve, 9000));
await processResults();

const outputDir = getOutputDir();
await uploadArtifacts(outputDir);
Expand Down
Loading