Skip to content
Closed
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
207 changes: 158 additions & 49 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
mod authorize;
mod cicd;
mod config;
mod scan;
mod wait;
mod list;
mod inspect;
mod cicd;
mod list;
mod log;
mod scan;
mod setup_hooks;
mod authorize;
mod wait;
mod scanners {
pub mod fortify;
pub mod blast;
pub mod fortify;
pub mod parsers;
}
mod utils {
pub mod terminal;
pub mod generic;
pub mod api;
pub mod generic;
pub mod terminal;
}
mod targets;

use std::str::FromStr;
use clap::{Parser, Subcommand, CommandFactory};
use clap::{CommandFactory, Parser, Subcommand};
use config::Config;
use scanners::fortify::parse as fortify_parse;
use std::str::FromStr;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
Expand All @@ -32,40 +32,59 @@ struct Cli {
command: Option<Commands>,

#[arg(required = false)]
args: Vec<String>,
args: Vec<String>,
}

#[derive(Subcommand, Debug)]
enum Commands {
/// Authenticate to Corgea
Login {
Login {
#[arg(help = "API token (if not provided, will use OAuth flow)")]
token: Option<String>,

#[arg(long, help = "The url of the corgea instance to use. defaults to https://www.corgea.app")]
#[arg(
long,
help = "The url of the corgea instance to use. defaults to https://www.corgea.app"
)]
url: Option<String>,

#[arg(long, help = "Scope to use for custom domain (e.g., 'ikea' for ikea.corgea.app). Only used with OAuth flow")]
#[arg(
long,
help = "Scope to use for custom domain (e.g., 'ikea' for ikea.corgea.app). Only used with OAuth flow"
)]
scope: Option<String>,
},
/// Upload a scan report to Corgea via STDIN or a file
Upload {
/// Option path to JSON report to upload
report: Option<String>,

#[arg(
long,
help = "The name of the Corgea project. Defaults to the existing upload behavior when omitted."
)]
project_name: Option<String>,
},
/// Scan the current directory. Supports blast, semgrep and snyk.
Scan {
/// What scanner to use. Valid options are blast, semgrep and snyk.
#[arg(default_value = "blast")]
scanner: Scanner,

#[arg(long, help = "Fail on (exits with error code 1) a specific severity level . Valid options are CR, HI, ME, LO.")]
#[arg(
long,
help = "Fail on (exits with error code 1) a specific severity level . Valid options are CR, HI, ME, LO."
)]
fail_on: Option<String>,

#[arg(long, help = "Only scan uncommitted changes.")]
only_uncommitted: bool,

#[arg(short, long, help = "Fail on (exits with error code 1) based on blocking rules defined in the web app.")]
#[arg(
short,
long,
help = "Fail on (exits with error code 1) based on blocking rules defined in the web app."
)]
fail: bool,

#[arg(
Expand All @@ -82,10 +101,17 @@ enum Commands {
)]
scan_type: Option<String>,

#[arg(long, help = "Output the result to a file in a specific format. Valid options are json, html, sarif, markdown.")]
#[arg(
long,
help = "Output the result to a file in a specific format. Valid options are json, html, sarif, markdown."
)]
out_format: Option<String>,

#[arg(short, long, help = "Output the result to a file. you can use the out_format option to specify the format of the output file.")]
#[arg(
short,
long,
help = "Output the result to a file. you can use the out_format option to specify the format of the output file."
)]
out_file: Option<String>,

#[arg(
Expand All @@ -101,16 +127,18 @@ enum Commands {
project_name: Option<String>,
},
/// Wait for the latest in progress scan
Wait {
scan_id: Option<String>,
},
Wait { scan_id: Option<String> },
/// List something, by default it lists the scans
#[command(alias = "ls")]
List {
#[arg(short, long, help = "List issues instead of scans")]
issues: bool,

#[arg(long, short = 'c', help = "List SCA (Software Composition Analysis) issues instead of regular issues")]
#[arg(
long,
short = 'c',
help = "List SCA (Software Composition Analysis) issues instead of regular issues"
)]
sca_issues: bool,

#[arg(short, long, help = "Specify the scan id to list issues for.")]
Expand All @@ -123,7 +151,7 @@ enum Commands {
json: bool,

#[arg(long, value_parser = clap::value_parser!(u16), help = "Number of items per page")]
page_size: Option<u16>
page_size: Option<u16>,
},
/// Inspect something, by default it will inspect a scan
Inspect {
Expand All @@ -134,20 +162,36 @@ enum Commands {
#[arg(long, help = "Output the result in JSON format.")]
json: bool,

#[arg(long, short, help = "Display a summary only of the issue in the output (only if --issue is true).")]
#[arg(
long,
short,
help = "Display a summary only of the issue in the output (only if --issue is true)."
)]
summary: bool,

#[arg(long, short, help = "Display the fix explanations only in the output (only if --issue is true).")]
#[arg(
long,
short,
help = "Display the fix explanations only in the output (only if --issue is true)."
)]
fix: bool,

#[arg(long, short, help = "Display the diff of the fix only in the output (only if --issue is true).")]
#[arg(
long,
short,
help = "Display the diff of the fix only in the output (only if --issue is true)."
)]
diff: bool,

id: String,
},
/// Setup a git hook, currently only pre-commit is supported
SetupHooks {
#[arg(long, short, help = "Include default config (scan types are pii, secrets and fail on levels are CR, HI, ME, LO).")]
#[arg(
long,
short,
help = "Include default config (scan types are pii, secrets and fail on levels are CR, HI, ME, LO)."
)]
default_config: bool,
},
}
Expand Down Expand Up @@ -175,7 +219,7 @@ impl FromStr for Scanner {
fn main() {
let cli = Cli::parse();
let mut corgea_config = Config::load().expect("Failed to load config");
fn verify_token_and_exit_when_fail (config: &Config) {
fn verify_token_and_exit_when_fail(config: &Config) {
if config.get_token().is_empty() {
eprintln!("No token set.\nPlease run 'corgea login' to authenticate.\nFor more info checkout our docs at Check out our docs at https://docs.corgea.app/install_cli#login-with-the-cli");
std::process::exit(1);
Expand All @@ -187,7 +231,7 @@ fn main() {
Ok(false) => {
println!("Invalid token provided.\nPlease run 'corgea login' to authenticate.\nFor more info checkout our docs at Check out our docs at https://docs.corgea.app/install_cli#login-with-the-cli");
std::process::exit(1);
},
}
Err(e) => {
eprintln!("Error occurred: {}", e);
std::process::exit(1);
Expand All @@ -196,18 +240,34 @@ fn main() {
}
match &cli.command {
Some(Commands::Login { token, url, scope }) => {
let effective_token = token.clone().or_else(|| utils::generic::get_env_var_if_exists("CORGEA_TOKEN"));

let effective_token = token
.clone()
.or_else(|| utils::generic::get_env_var_if_exists("CORGEA_TOKEN"));

match effective_token {
Some(token_value) => {
let token_source = if token.is_some() { "parameter" } else { "CORGEA_TOKEN environment variable" };
match utils::api::verify_token(&token_value, url.as_deref().unwrap_or(corgea_config.get_url().as_str())) {
let token_source = if token.is_some() {
"parameter"
} else {
"CORGEA_TOKEN environment variable"
};
match utils::api::verify_token(
&token_value,
url.as_deref().unwrap_or(corgea_config.get_url().as_str()),
) {
Ok(true) => {
corgea_config.set_token(token_value.clone()).expect("Failed to set token");
corgea_config
.set_token(token_value.clone())
.expect("Failed to set token");
if let Some(url) = url {
corgea_config.set_url(url.clone()).expect("Failed to set url");
corgea_config
.set_url(url.clone())
.expect("Failed to set url");
}
println!("Successfully authenticated to Corgea using token from {}.", token_source)
println!(
"Successfully authenticated to Corgea using token from {}.",
token_source
)
}
Ok(false) => println!("Invalid token provided from {}.", token_source),
Err(e) => {
Expand All @@ -217,17 +277,17 @@ fn main() {
}
eprintln!("Error occurred: {}", e);
std::process::exit(1);
},
}
}
}
// No token available - use OAuth flow
None => {
if url.is_some() && scope.is_some() {
eprintln!("Warning: --url option is ignored when using OAuth flow with --scope. The scope determines the domain.");
}

match authorize::run(scope.clone(), url.clone()) {
Ok(()) => {},
Ok(()) => {}
Err(e) => {
eprintln!("Authorization failed: {}", e);
std::process::exit(1);
Expand All @@ -236,22 +296,36 @@ fn main() {
}
}
}
Some(Commands::Upload { report }) => {
Some(Commands::Upload {
report,
project_name,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
match report {
Some(report) => {
if report.ends_with(".fpr") {
fortify_parse(&corgea_config, report);
fortify_parse(&corgea_config, report, project_name.as_deref());
} else {
scan::read_file_report(&corgea_config, report);
scan::read_file_report(&corgea_config, report, project_name.as_deref());
}
}
None => {
scan::read_stdin_report(&corgea_config);
scan::read_stdin_report(&corgea_config, project_name.as_deref());
}
}
}
Some(Commands::Scan { scanner , fail_on, fail, only_uncommitted, scan_type, policy, out_format, out_file, target, project_name }) => {
Some(Commands::Scan {
scanner,
fail_on,
fail,
only_uncommitted,
scan_type,
policy,
out_format,
out_file,
target,
project_name,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
if let Some(level) = fail_on {
if *scanner != Scanner::Blast {
Expand Down Expand Up @@ -284,7 +358,9 @@ fn main() {
std::process::exit(1);
}

if out_file.is_some() && !out_format.is_some() || !out_file.is_some() && out_format.is_some() {
if out_file.is_some() && !out_format.is_some()
|| !out_file.is_some() && out_format.is_some()
{
eprintln!("out_file and out_format must be used together.");
std::process::exit(1);
}
Expand Down Expand Up @@ -334,14 +410,32 @@ fn main() {
match scanner {
Scanner::Snyk => scan::run_snyk(&corgea_config),
Scanner::Semgrep => scan::run_semgrep(&corgea_config),
Scanner::Blast => scanners::blast::run(&corgea_config, fail_on.clone(), fail, only_uncommitted, scan_type.clone(), policy.clone(), out_format.clone(), out_file.clone(), target.clone(), project_name.clone())
Scanner::Blast => scanners::blast::run(
&corgea_config,
fail_on.clone(),
fail,
only_uncommitted,
scan_type.clone(),
policy.clone(),
out_format.clone(),
out_file.clone(),
target.clone(),
project_name.clone(),
),
}
}
Some(Commands::Wait { scan_id }) => {
verify_token_and_exit_when_fail(&corgea_config);
wait::run(&corgea_config, scan_id.clone(), None);
}
Some(Commands::List { issues , json, page, page_size, scan_id, sca_issues}) => {
Some(Commands::List {
issues,
json,
page,
page_size,
scan_id,
sca_issues,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
if *issues && *sca_issues {
eprintln!("Cannot use both --issues and --sca-issues at the same time.");
Expand All @@ -351,9 +445,24 @@ fn main() {
println!("scan_id option is only supported for issues list command.");
std::process::exit(1);
}
list::run(&corgea_config, issues, sca_issues, json, page, page_size, scan_id);
list::run(
&corgea_config,
issues,
sca_issues,
json,
page,
page_size,
scan_id,
);
}
Some(Commands::Inspect { issue, json, id, summary, fix, diff }) => {
Some(Commands::Inspect {
issue,
json,
id,
summary,
fix,
diff,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
inspect::run(&corgea_config, issue, json, summary, fix, diff, id)
}
Expand Down
Loading
Loading