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: Secret detection #16

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
145 changes: 145 additions & 0 deletions src/secret-detect/cmd/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use std::time::Instant;
use std::process::exit;

use clap::{arg, ArgMatches, Command};
use log::{error, info, LevelFilter};
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use std::io::{self, Read};

use crate::config::Config;
use crate::detect::Detector;
use crate::report::{Finding, FindingSummary};
use crate::sources::{DirectoryTargets, GitLogCmd, PipeReader};

// Function to initialize logging based on verbosity level
fn init_logging(verbose: u64) {
let log_level = match verbose {
0 => LevelFilter::Info,
1 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};

let config = ConfigBuilder::new()
.set_target_level(log_level)
.set_thread_level(LevelFilter::Off)
.build();

TermLogger::init(
log_level,
config,
TerminalMode::Mixed,
ColorChoice::Auto,
)
.unwrap();
}

// Function to run the detect command
pub fn run_detect(matches: &ArgMatches) {
let start_time = Instant::now();

// Initialize logging
init_logging(matches.get_count("verbose") as u64);

// Load the configuration
let config = match Config::load_from_file(matches.get_one::<String>("config").unwrap()) {
Ok(cfg) => cfg,
Err(e) => {
error!("Failed to load configuration: {}", e);
exit(1);
}
};

// Get source path
let source_path = matches.get_one::<String>("source").unwrap();

// Create the detector
let mut detector = Detector::new(&config, source_path);

// Determine the scan type
let no_git = matches.get_flag("no-git");
let from_pipe = matches.get_flag("pipe");

// Perform the scan
let findings: Vec<Finding> = if no_git {
// Scan directory
match DirectoryTargets::new(source_path, detector.sema.clone(), detector.follow_symlinks) {
Ok(paths) => match detector.detect_files(&paths) {
Ok(f) => f,
Err(e) => {
error!("Failed to scan directory: {}", e);
vec![] // Return an empty vector on error
}
},
Err(e) => {
error!("Failed to get directory targets: {}", e);
exit(1);
}
}
} else if from_pipe {
// Scan from pipe
let mut buffer = String::new();
match io::stdin().read_to_string(&mut buffer) {
Ok(_) => match detector.detect_reader(&buffer) {
Ok(f) => f,
Err(e) => {
error!("Failed to scan from pipe: {}", e);
exit(1);
}
},
Err(e) => {
error!("Failed to read from pipe: {}", e);
exit(1);
}
}
} else {
// Scan git history
let log_opts = matches.get_one::<String>("log-opts").unwrap();
match GitLogCmd::new(source_path, log_opts) {
Ok(git_cmd) => match detector.detect_git(&git_cmd) {
Ok(f) => f,
Err(e) => {
error!("Failed to scan git history: {}", e);
vec![] // Return an empty vector on error
}
},
Err(e) => {
error!("Failed to create git log command: {}", e);
exit(1);
}
}
};

// Create the finding summary
let finding_summary = FindingSummary::new(&findings, start_time.elapsed(), config);

// Handle the findings and exit
finding_summary.handle_findings(&matches);
}

// Function to write the configuration to a file
fn write_config_to_file(config: &Config, output_path: &str) -> Result<(), std::io::Error> {
let mut file = File::create(output_path)?;
// Template file or logic to generate the TOML content
let toml_content = config.generate_toml();
file.write_all(toml_content.as_bytes())?;
Ok(())
}

// CLI definition using clap
pub fn cli() -> Command {
Command::new("gitleaks")
.about("Detect secrets in code")
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("detect")
.about("Detect secrets")
.arg(arg!(-c --config <CONFIG> "Path to gitleaks.toml config file").required(true))
.arg(arg!(-s --source <SOURCE> "Source to scan (file, directory, or git repository URL)").required(true))
.arg(arg!(--no-git "Treat git repo as a regular directory"))
.arg(arg!(--pipe "Scan input from stdin"))
.arg(arg!(-l --log-opts <LOG_OPTS> "Git log options (only applicable for git scans)"))
.arg(arg!(-e --exit-code <EXIT_CODE> "Exit code to use if secrets are found (default: 1)").default_value("1"))
.arg(arg!(-v ... "Increase verbosity level"))
)
}
228 changes: 228 additions & 0 deletions src/secret-detect/cmd/generate/config/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;

use log::{error, info};

use crate::config::{Allowlist, Config, Rule};
use crate::rules::*;

// Assuming rules module contains all the rule functions

const TEMPLATE_PATH: &str = "rules/config.tmpl";

// Entry point for generating the keyshade.toml configuration file
pub fn generate_config_file(output_path: &str) {
let mut config = Config::default();
config.rules = build_rule_lookup();

if let Err(e) = write_config_to_file(&config, output_path) {
error!("Failed to write config to file: {}", e);
} else {
info!("Successfully generated keyshade.toml at {}", output_path);
}
}

// Build a HashMap of rules, ensuring unique rule ID
fn build_rule_lookup() -> HashMap<String, Rule> {
let mut rule_lookup = HashMap::new();

// Macro to add rules to the lookup table
macro_rules! add_rule {
($rule_fn:ident) => {
let rule = $rule_fn();
if rule_lookup.insert(rule.rule_id.clone(), rule).is_some() {
error!("Rule ID {} is not unique", rule.rule_id);
}
};
}

// Add each rule using the macro
aadd_rule!(adafruit_api_key);
add_rule!(adobe_client_id);
add_rule!(adobe_client_secret);
add_rule!(age_secret_key);
add_rule!(airtable);
add_rule!(algolia_api_key);
add_rule!(alibaba_access_key);
add_rule!(alibaba_secret_key);
add_rule!(asana_client_id);
add_rule!(asana_client_secret);
add_rule!(atlassian);
add_rule!(authress);
add_rule!(aws);
add_rule!(beamer);
add_rule!(bitbucket_client_id);
add_rule!(bitbucket_client_secret);
add_rule!(bittrex_access_key);
add_rule!(bittrex_secret_key);
add_rule!(clojars);
add_rule!(cloudflare_global_api_key);
add_rule!(cloudflare_api_key);
add_rule!(cloudflare_origin_ca_key);
add_rule!(codecov_access_token);
add_rule!(coinbase_access_token);
add_rule!(confluent_secret_key);
add_rule!(confluent_access_token);
add_rule!(contentful);
add_rule!(databricks);
add_rule!(datadogtoken_access_token);
add_rule!(defined_networking_api_token);
add_rule!(digitalocean_pat);
add_rule!(digitalocean_oauth_token);
add_rule!(digitalocean_refresh_token);
add_rule!(discord_api_token);
add_rule!(discord_client_id);
add_rule!(discord_client_secret);
add_rule!(doppler);
add_rule!(drop_box_api_secret);
add_rule!(drop_box_long_lived_api_token);
add_rule!(drop_box_short_lived_api_token);
add_rule!(droneci_access_token);
add_rule!(duffel);
add_rule!(dynatrace);
add_rule!(easy_post);
add_rule!(easy_post_test_api);
add_rule!(etsy_access_token);
add_rule!(facebook_secret);
add_rule!(facebook_access_token);
add_rule!(facebook_page_access_token);
add_rule!(fastly_api_token);
add_rule!(finicity_api_token);
add_rule!(finicity_client_secret);
add_rule!(finnhub_access_token);
add_rule!(flickr_access_token);
add_rule!(flutterwave_enc_key);
add_rule!(flutterwave_public_key);
add_rule!(flutterwave_secret_key);
add_rule!(frame_io);
add_rule!(freshbooks_access_token);
add_rule!(gcp_api_key);
add_rule!(gcp_service_account);
add_rule!(generic_credential);
add_rule!(github_app);
add_rule!(github_fine_grained_pat);
add_rule!(github_oauth);
add_rule!(github_pat);
add_rule!(github_refresh);
add_rule!(gitlab_pat);
add_rule!(gitlab_pipeline_trigger_token);
add_rule!(gitlab_runner_registration_token);
add_rule!(hashicorp);
add_rule!(hashicorp_field);
add_rule!(heroku);
add_rule!(hubspot);
add_rule!(hugging_face_access_token);
add_rule!(hugging_face_organization_api_token);
add_rule!(infracost_api_token);
add_rule!(intercom);
add_rule!(jfrog_api_key);
add_rule!(jfrog_identity_token);
add_rule!(jwt);
add_rule!(jwt_base64);
add_rule!(kraken_access_token);
add_rule!(kucoin_access_token);
add_rule!(kucoin_secret_key);
add_rule!(launchdarkly_access_token);
add_rule!(linear_api_token);
add_rule!(linear_client_secret);
add_rule!(linkedin_client_id);
add_rule!(linkedin_client_secret);
add_rule!(lob_api_token);
add_rule!(lob_pub_api_token);
add_rule!(mailchimp);
add_rule!(mailgun_private_api_token);
add_rule!(mailgun_pub_api_token);
add_rule!(mailgun_signing_key);
add_rule!(mapbox);
add_rule!(mattermost_access_token);
add_rule!(messagebird_api_token);
add_rule!(messagebird_client_id);
add_rule!(netlify_access_token);
add_rule!(new_relic_browser_api_key);
add_rule!(new_relic_user_id);
add_rule!(new_relic_user_key);
add_rule!(npm);
add_rule!(nytimes_access_token);
add_rule!(okta_access_token);
add_rule!(openai);
add_rule!(plaid_access_id);
add_rule!(plaid_secret_key);
add_rule!(plaid_access_token);
add_rule!(planetscale_api_token);
add_rule!(planetscale_oauth_token);
add_rule!(planetscale_password);
add_rule!(postman_api);
add_rule!(prefect);
add_rule!(private_key);
add_rule!(pulumi_api_token);
add_rule!(pypi_upload_token);
add_rule!(rapidapi_access_token);
add_rule!(readme);
add_rule!(rubygems_api_token);
add_rule!(scalingo_api_token);
add_rule!(sendbird_access_id);
add_rule!(sendbird_access_token);
add_rule!(sendgrid_api_token);
add_rule!(sendinblue_api_token);
add_rule!(sentry_access_token);
add_rule!(shippo_api_token);
add_rule!(shopify_access_token);
add_rule!(shopify_custom_access_token);
add_rule!(shopify_private_app_access_token);
add_rule!(shopify_shared_secret);
add_rule!(sidekiq_secret);
add_rule!(sidekiq_sensitive_url);
add_rule!(slack_app_level_token);
add_rule!(slack_bot_token);
add_rule!(slack_configuration_refresh_token);
add_rule!(slack_configuration_token);
add_rule!(slack_legacy_bot_token);
add_rule!(slack_legacy_token);
add_rule!(slack_legacy_workspace_token);
add_rule!(slack_user_token);
add_rule!(slack_webhook_url);
add_rule!(snyk);
add_rule!(square_access_token);
add_rule!(square_secret);
add_rule!(squarespace_access_token);
add_rule!(sumologic_access_id);
add_rule!(sumologic_access_token);
add_rule!(teams_webhook);
add_rule!(telegram_bot_token);
add_rule!(travis_ci_access_token);
add_rule!(trello_access_token);
add_rule!(twilio);
add_rule!(twitch_api_token);
add_rule!(twitter_api_key);
add_rule!(twitter_api_secret);
add_rule!(twitter_access_secret);
add_rule!(twitter_access_token);
add_rule!(twitter_bearer_token);
add_rule!(typeform);
add_rule!(vault_batch_token);
add_rule!(vault_service_token);
add_rule!(yandex_api_key);
add_rule!(yandex_aws_access_token);
add_rule!(yandex_access_token);
add_rule!(zendesk_secret_key);

rule_lookup
}

// Write the configuration to a TOML file
fn write_config_to_file(config: &Config, output_path: &str) -> Result<(), std::io::Error> {
let template = include_str!(TEMPLATE_PATH);
let rendered_config = match tera::Tera::one_off(template, &tera::Context::from_serialize(config).unwrap(), true) {
Ok(s) => s,
Err(e) => {
error!("Failed to render config template: {}", e);
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Template rendering failed"));
}
};

let mut file = File::create(output_path)?;
file.write_all(rendered_config.as_bytes())?;

Ok(())
}
Loading