forked from mirrors/kingfisher
- Added '--repo-artifacts' flag to scan repository issues, gists/snippets, and wikis when cloning via '--git-url'
This commit is contained in:
parent
32b11dd409
commit
6e4c94ddc3
19 changed files with 470 additions and 22 deletions
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.45.0]
|
||||
- Added `--repo-artifacts` flag to scan repository issues, gists/snippets, and wikis when cloning via `--git-url`
|
||||
|
||||
## [1.44.0]
|
||||
- Fixed issue with self-update on Linux
|
||||
- Reverted the change to json and jsonl outputs by rule
|
||||
|
|
|
|||
25
README.md
25
README.md
|
|
@ -369,12 +369,21 @@ kingfisher scan --github-organization my-org
|
|||
|
||||
### Scan remote GitHub repository
|
||||
|
||||
`--git-url` clones the repository and scans its files and history. To also inspect
|
||||
related server-side data, supply `--repo-artifacts`. This flag pulls down the
|
||||
repository's issues (including pull requests), wiki, and any public gists owned by
|
||||
the repository owner and scans them for secrets. Fetching these extras counts
|
||||
against API rate limits and private artifacts require a `KF_GITHUB_TOKEN`.
|
||||
|
||||
```bash
|
||||
# Scan the repository only
|
||||
kingfisher scan --git-url https://github.com/org/repo.git
|
||||
|
||||
# Optionally provide a GitHub Token
|
||||
KF_GITHUB_TOKEN="ghp_…" kingfisher scan --git-url https://github.com/org/private_repo.git
|
||||
# Include issues, wiki, and owner gists
|
||||
kingfisher scan --git-url https://github.com/org/repo.git --repo-artifacts
|
||||
|
||||
# Private repositories or artifacts
|
||||
KF_GITHUB_TOKEN="ghp_…" kingfisher scan --git-url https://github.com/org/private_repo.git --repo-artifacts
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -397,8 +406,20 @@ kingfisher scan --gitlab-user johndoe
|
|||
|
||||
### Scan remote GitLab repository by URL
|
||||
|
||||
`--git-url` by itself clones the project repository. To include server-side
|
||||
artifacts owned by the project, add `--repo-artifacts`. Kingfisher will retrieve
|
||||
the project's issues, wiki, and snippets and scan them for secrets. These extra
|
||||
requests may take longer and require a `KF_GITLAB_TOKEN` for private projects.
|
||||
|
||||
```bash
|
||||
# Scan the repository only
|
||||
kingfisher scan --git-url https://gitlab.com/group/project.git
|
||||
|
||||
# Include issues, wiki, and snippets
|
||||
kingfisher scan --git-url https://gitlab.com/group/project.git --repo-artifacts
|
||||
|
||||
# Private projects or artifacts
|
||||
KF_GITLAB_TOKEN="glpat-…" kingfisher scan --git-url https://gitlab.com/group/private_project.git --repo-artifacts
|
||||
```
|
||||
|
||||
### List GitLab repositories
|
||||
|
|
|
|||
|
|
@ -154,6 +154,10 @@ pub struct InputSpecifierArgs {
|
|||
#[arg(long, default_value_t = true, action = clap::ArgAction::Set, help_heading = "Git Options")]
|
||||
pub commit_metadata: bool,
|
||||
|
||||
/// Also scan repository host artifacts like issues, wikis, and gists/snippets
|
||||
#[arg(long, help_heading = "Git Options")]
|
||||
pub repo_artifacts: bool,
|
||||
|
||||
/// Enable or disable scanning nested git repositories
|
||||
#[arg(long, default_value_t = true)]
|
||||
pub scan_nested_repos: bool,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ pub struct FindingsStore {
|
|||
slack_links: FxHashMap<PathBuf, String>,
|
||||
confluence_links: FxHashMap<PathBuf, String>,
|
||||
s3_buckets: FxHashMap<PathBuf, String>,
|
||||
repo_links: FxHashMap<PathBuf, String>,
|
||||
}
|
||||
impl FindingsStore {
|
||||
pub fn new(clone_dir: PathBuf) -> Self {
|
||||
|
|
@ -77,6 +78,7 @@ impl FindingsStore {
|
|||
slack_links: FxHashMap::default(),
|
||||
confluence_links: FxHashMap::default(),
|
||||
s3_buckets: FxHashMap::default(),
|
||||
repo_links: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -318,6 +320,14 @@ impl FindingsStore {
|
|||
&self.confluence_links
|
||||
}
|
||||
|
||||
pub fn register_repo_link(&mut self, path: PathBuf, link: String) {
|
||||
self.repo_links.insert(path, link);
|
||||
}
|
||||
|
||||
pub fn repo_links(&self) -> &FxHashMap<PathBuf, String> {
|
||||
&self.repo_links
|
||||
}
|
||||
|
||||
pub fn register_s3_bucket(&mut self, dir: PathBuf, bucket: String) {
|
||||
self.s3_buckets.insert(dir, bucket);
|
||||
}
|
||||
|
|
|
|||
199
src/github.rs
199
src/github.rs
|
|
@ -1,4 +1,10 @@
|
|||
use std::{env, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
|
|
@ -7,8 +13,12 @@ use octorust::{
|
|||
types::{Order, ReposListOrgSort, ReposListOrgType, ReposListUserType},
|
||||
Client,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use crate::{findings_store, git_url::GitUrl};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RepoSpecifiers {
|
||||
pub user: Vec<String>,
|
||||
|
|
@ -161,3 +171,190 @@ pub async fn list_repositories(
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_repo(repo_url: &GitUrl) -> Option<(String, String, String)> {
|
||||
let url = Url::parse(repo_url.as_str()).ok()?;
|
||||
let host = url.host_str()?.to_string();
|
||||
let mut segments = url.path_segments()?;
|
||||
let owner = segments.next()?.to_string();
|
||||
let mut repo = segments.next()?.to_string();
|
||||
if let Some(stripped) = repo.strip_suffix(".git") {
|
||||
repo = stripped.to_string();
|
||||
}
|
||||
Some((host, owner, repo))
|
||||
}
|
||||
|
||||
pub fn wiki_url(repo_url: &GitUrl) -> Option<GitUrl> {
|
||||
let (host, owner, repo) = parse_repo(repo_url)?;
|
||||
let wiki = format!("https://{host}/{owner}/{repo}.wiki.git");
|
||||
GitUrl::from_str(&wiki).ok()
|
||||
}
|
||||
|
||||
pub async fn fetch_repo_items(
|
||||
repo_url: &GitUrl,
|
||||
ignore_certs: bool,
|
||||
output_root: &Path,
|
||||
datastore: &Arc<Mutex<findings_store::FindingsStore>>,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let (_, owner, repo) = parse_repo(repo_url).context("invalid GitHub repo URL")?;
|
||||
let client = reqwest::Client::builder().danger_accept_invalid_certs(ignore_certs).build()?;
|
||||
|
||||
let mut dirs = Vec::new();
|
||||
|
||||
// Issues
|
||||
let issues_dir = output_root.join("github_issues").join(&owner).join(&repo);
|
||||
fs::create_dir_all(&issues_dir)?;
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let url = format!(
|
||||
"https://api.github.com/repos/{owner}/{repo}/issues?state=all&per_page=100&page={page}"
|
||||
);
|
||||
let mut req = client.get(&url).header("User-Agent", "kingfisher");
|
||||
if let Ok(token) = env::var("KF_GITHUB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req = req.bearer_auth(token);
|
||||
}
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
if !resp.status().is_success() {
|
||||
break;
|
||||
}
|
||||
let issues: Vec<Value> = resp.json().await?;
|
||||
if issues.is_empty() {
|
||||
break;
|
||||
}
|
||||
for issue in issues {
|
||||
let number = issue.get("number").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let title = issue.get("title").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let body = issue.get("body").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let content = format!("# {title}\n\n{body}");
|
||||
let file_path = issues_dir.join(format!("issue_{number}.md"));
|
||||
fs::write(&file_path, content)?;
|
||||
let url = format!("https://github.com/{owner}/{repo}/issues/{number}");
|
||||
let mut ds = datastore.lock().unwrap();
|
||||
ds.register_repo_link(file_path, url);
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
if issues_dir.read_dir().ok().and_then(|mut d| d.next()).is_some() {
|
||||
dirs.push(issues_dir);
|
||||
}
|
||||
|
||||
// Gists
|
||||
let gists_dir = output_root.join("github_gists").join(&owner);
|
||||
fs::create_dir_all(&gists_dir)?;
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
// Public gists for the owner
|
||||
page = 1;
|
||||
loop {
|
||||
let url = format!("https://api.github.com/users/{owner}/gists?per_page=100&page={page}");
|
||||
let mut req = client.get(&url).header("User-Agent", "kingfisher");
|
||||
if let Ok(token) = env::var("KF_GITHUB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req = req.bearer_auth(&token);
|
||||
}
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
if !resp.status().is_success() {
|
||||
break;
|
||||
}
|
||||
let gists: Vec<Value> = resp.json().await?;
|
||||
if gists.is_empty() {
|
||||
break;
|
||||
}
|
||||
for gist in gists {
|
||||
if let Some(id) = gist.get("id").and_then(|v| v.as_str()) {
|
||||
if seen.insert(id.to_string()) {
|
||||
let mut req_g = client
|
||||
.get(&format!("https://api.github.com/gists/{id}"))
|
||||
.header("User-Agent", "kingfisher");
|
||||
if let Ok(token) = env::var("KF_GITHUB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req_g = req_g.bearer_auth(&token);
|
||||
}
|
||||
}
|
||||
let detail: Value = req_g.send().await?.json().await?;
|
||||
if let Some(files) = detail.get("files").and_then(|v| v.as_object()) {
|
||||
let gist_dir = gists_dir.join(id);
|
||||
fs::create_dir_all(&gist_dir)?;
|
||||
for (fname, fobj) in files {
|
||||
if let Some(content) = fobj.get("content").and_then(|v| v.as_str()) {
|
||||
let file_path = gist_dir.join(fname);
|
||||
fs::write(&file_path, content)?;
|
||||
let url = format!("https://gist.github.com/{id}");
|
||||
let mut ds = datastore.lock().unwrap();
|
||||
ds.register_repo_link(file_path, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
|
||||
// Private gists for authenticated user if they own the repo
|
||||
if let Ok(token) = env::var("KF_GITHUB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
page = 1;
|
||||
loop {
|
||||
let url = format!("https://api.github.com/gists?per_page=100&page={page}");
|
||||
let resp = client
|
||||
.get(&url)
|
||||
.header("User-Agent", "kingfisher")
|
||||
.bearer_auth(&token)
|
||||
.send()
|
||||
.await?;
|
||||
if !resp.status().is_success() {
|
||||
break;
|
||||
}
|
||||
let gists: Vec<Value> = resp.json().await?;
|
||||
if gists.is_empty() {
|
||||
break;
|
||||
}
|
||||
for gist in gists {
|
||||
let owner_login =
|
||||
gist.get("owner").and_then(|o| o.get("login")).and_then(|v| v.as_str());
|
||||
if owner_login == Some(owner.as_str()) {
|
||||
if let Some(id) = gist.get("id").and_then(|v| v.as_str()) {
|
||||
if seen.insert(id.to_string()) {
|
||||
let detail: Value = client
|
||||
.get(&format!("https://api.github.com/gists/{id}"))
|
||||
.header("User-Agent", "kingfisher")
|
||||
.bearer_auth(&token)
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
if let Some(files) = detail.get("files").and_then(|v| v.as_object())
|
||||
{
|
||||
let gist_dir = gists_dir.join(id);
|
||||
fs::create_dir_all(&gist_dir)?;
|
||||
for (fname, fobj) in files {
|
||||
if let Some(content) =
|
||||
fobj.get("content").and_then(|v| v.as_str())
|
||||
{
|
||||
let file_path = gist_dir.join(fname);
|
||||
fs::write(&file_path, content)?;
|
||||
let url = format!("https://gist.github.com/{id}");
|
||||
let mut ds = datastore.lock().unwrap();
|
||||
ds.register_repo_link(file_path, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gists_dir.read_dir().ok().and_then(|mut d| d.next()).is_some() {
|
||||
dirs.push(gists_dir);
|
||||
}
|
||||
|
||||
Ok(dirs)
|
||||
}
|
||||
|
|
|
|||
127
src/gitlab.rs
127
src/gitlab.rs
|
|
@ -1,4 +1,9 @@
|
|||
use std::{env, time::Duration};
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use gitlab::{
|
||||
|
|
@ -12,7 +17,11 @@ use gitlab::{
|
|||
};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
use serde_json::Value;
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
use crate::{findings_store, git_url::GitUrl};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SimpleUser {
|
||||
|
|
@ -197,3 +206,117 @@ pub async fn list_repositories(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_repo(repo_url: &GitUrl) -> Option<(String, String)> {
|
||||
let url = Url::parse(repo_url.as_str()).ok()?;
|
||||
let host = url.host_str()?.to_string();
|
||||
let mut path = url.path().trim_start_matches('/').to_string();
|
||||
if let Some(stripped) = path.strip_suffix(".git") {
|
||||
path = stripped.to_string();
|
||||
}
|
||||
Some((host, path))
|
||||
}
|
||||
|
||||
pub fn wiki_url(repo_url: &GitUrl) -> Option<GitUrl> {
|
||||
let (host, path) = parse_repo(repo_url)?;
|
||||
let wiki = format!("https://{host}/{path}.wiki.git");
|
||||
GitUrl::from_str(&wiki).ok()
|
||||
}
|
||||
|
||||
pub async fn fetch_repo_items(
|
||||
repo_url: &GitUrl,
|
||||
ignore_certs: bool,
|
||||
output_root: &Path,
|
||||
datastore: &Arc<Mutex<findings_store::FindingsStore>>,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let (host, path) = parse_repo(repo_url).context("invalid GitLab repo URL")?;
|
||||
let encoded = form_urlencoded::byte_serialize(path.as_bytes()).collect::<String>();
|
||||
let client = reqwest::Client::builder().danger_accept_invalid_certs(ignore_certs).build()?;
|
||||
|
||||
let mut dirs = Vec::new();
|
||||
|
||||
// Issues
|
||||
let issues_dir = output_root.join("gitlab_issues").join(path.replace('/', "_"));
|
||||
fs::create_dir_all(&issues_dir)?;
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let url = format!(
|
||||
"https://{host}/api/v4/projects/{encoded}/issues?scope=all&state=all&per_page=100&page={page}"
|
||||
);
|
||||
let mut req = client.get(&url);
|
||||
if let Ok(token) = env::var("KF_GITLAB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req = req.header("PRIVATE-TOKEN", token);
|
||||
}
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
if !resp.status().is_success() {
|
||||
break;
|
||||
}
|
||||
let issues: Vec<Value> = resp.json().await?;
|
||||
if issues.is_empty() {
|
||||
break;
|
||||
}
|
||||
for issue in issues {
|
||||
let number = issue.get("iid").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let title = issue.get("title").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let body = issue.get("description").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let content = format!("# {title}\n\n{body}");
|
||||
let file_path = issues_dir.join(format!("issue_{number}.md"));
|
||||
fs::write(&file_path, content)?;
|
||||
let url = format!("https://{host}/{path}/-/issues/{number}");
|
||||
let mut ds = datastore.lock().unwrap();
|
||||
ds.register_repo_link(file_path, url);
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
if issues_dir.read_dir().ok().and_then(|mut d| d.next()).is_some() {
|
||||
dirs.push(issues_dir);
|
||||
}
|
||||
|
||||
// Snippets
|
||||
let snippets_dir = output_root.join("gitlab_snippets").join(path.replace('/', "_"));
|
||||
fs::create_dir_all(&snippets_dir)?;
|
||||
page = 1;
|
||||
loop {
|
||||
let url =
|
||||
format!("https://{host}/api/v4/projects/{encoded}/snippets?per_page=100&page={page}");
|
||||
let mut req = client.get(&url);
|
||||
if let Ok(token) = env::var("KF_GITLAB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req = req.header("PRIVATE-TOKEN", token);
|
||||
}
|
||||
}
|
||||
let resp = req.send().await?;
|
||||
if !resp.status().is_success() {
|
||||
break;
|
||||
}
|
||||
let snippets: Vec<Value> = resp.json().await?;
|
||||
if snippets.is_empty() {
|
||||
break;
|
||||
}
|
||||
for snip in snippets {
|
||||
if let Some(id) = snip.get("id").and_then(|v| v.as_u64()) {
|
||||
let raw_url = format!("https://{host}/api/v4/projects/{encoded}/snippets/{id}/raw");
|
||||
let mut req_s = client.get(&raw_url);
|
||||
if let Ok(token) = env::var("KF_GITLAB_TOKEN") {
|
||||
if !token.is_empty() {
|
||||
req_s = req_s.header("PRIVATE-TOKEN", token);
|
||||
}
|
||||
}
|
||||
let raw = req_s.send().await?.text().await?;
|
||||
let file_path = snippets_dir.join(format!("snippet_{id}"));
|
||||
fs::write(&file_path, raw)?;
|
||||
let url = format!("https://{host}/{path}/-/snippets/{id}");
|
||||
let mut ds = datastore.lock().unwrap();
|
||||
ds.register_repo_link(file_path, url);
|
||||
}
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
if snippets_dir.read_dir().ok().and_then(|mut d| d.next()).is_some() {
|
||||
dirs.push(snippets_dir);
|
||||
}
|
||||
|
||||
Ok(dirs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -305,8 +305,9 @@ fn create_default_scan_args() -> cli::commands::scan::ScanArgs {
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -148,6 +148,11 @@ impl DetailsReporter {
|
|||
ds.slack_links().get(path).cloned()
|
||||
}
|
||||
|
||||
fn repo_artifact_url(&self, path: &std::path::Path) -> Option<String> {
|
||||
let ds = self.datastore.lock().ok()?;
|
||||
ds.repo_links().get(path).cloned()
|
||||
}
|
||||
|
||||
fn s3_display_path(&self, path: &std::path::Path) -> Option<String> {
|
||||
let ds = self.datastore.lock().ok()?;
|
||||
for (dir, bucket) in ds.s3_buckets().iter() {
|
||||
|
|
@ -338,7 +343,9 @@ impl DetailsReporter {
|
|||
.iter()
|
||||
.find_map(|origin| match origin {
|
||||
Origin::File(e) => {
|
||||
if let Some(url) = self.jira_issue_url(&e.path, args) {
|
||||
if let Some(url) = self.repo_artifact_url(&e.path) {
|
||||
Some(url)
|
||||
} else if let Some(url) = self.jira_issue_url(&e.path, args) {
|
||||
Some(url)
|
||||
} else if let Some(url) = self.confluence_page_url(&e.path) {
|
||||
Some(url)
|
||||
|
|
|
|||
|
|
@ -105,8 +105,9 @@ mod tests {
|
|||
// clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use anyhow::{Context, Result};
|
|||
use indicatif::{HumanCount, ProgressBar, ProgressStyle};
|
||||
use tokio::time::Duration;
|
||||
use tracing::{debug, error, info};
|
||||
use url::Url;
|
||||
|
||||
use crate::blob::BlobIdMap;
|
||||
use crate::{
|
||||
|
|
@ -102,7 +103,12 @@ pub fn clone_or_update_git_repos(
|
|||
progress.suspend(|| info!("Cloning {repo_url}..."));
|
||||
if let Err(e) = git.create_fresh_clone(repo_url, &output_dir, clone_mode) {
|
||||
progress.suspend(|| {
|
||||
error!("Failed to clone {repo_url} to {}: {e}", output_dir.display());
|
||||
if repo_url.as_str().ends_with(".wiki.git") {
|
||||
info!("Wiki repository not found for {repo_url}, skipping");
|
||||
debug!("Failed to clone {repo_url} to {}: {e}", output_dir.display());
|
||||
} else {
|
||||
error!("Failed to clone {repo_url} to {}: {e}", output_dir.display());
|
||||
}
|
||||
debug!("Skipping scan of {repo_url}");
|
||||
});
|
||||
progress.inc(1);
|
||||
|
|
@ -328,6 +334,46 @@ pub async fn fetch_slack_messages(
|
|||
Ok(vec![output_dir])
|
||||
}
|
||||
|
||||
pub async fn fetch_git_host_artifacts(
|
||||
repo_urls: &[GitUrl],
|
||||
global_args: &global::GlobalArgs,
|
||||
datastore: &Arc<Mutex<findings_store::FindingsStore>>,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let output_root = {
|
||||
let ds = datastore.lock().unwrap();
|
||||
ds.clone_root()
|
||||
};
|
||||
let mut dirs = Vec::new();
|
||||
for repo_url in repo_urls {
|
||||
let host = Url::parse(repo_url.as_str())
|
||||
.ok()
|
||||
.and_then(|u| u.host_str().map(|s| s.to_string()))
|
||||
.unwrap_or_default();
|
||||
if host.contains("github") {
|
||||
dirs.extend(
|
||||
github::fetch_repo_items(
|
||||
repo_url,
|
||||
global_args.ignore_certs,
|
||||
&output_root,
|
||||
datastore,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
} else if host.contains("gitlab") {
|
||||
dirs.extend(
|
||||
gitlab::fetch_repo_items(
|
||||
repo_url,
|
||||
global_args.ignore_certs,
|
||||
&output_root,
|
||||
datastore,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(dirs)
|
||||
}
|
||||
|
||||
pub async fn fetch_s3_objects(
|
||||
args: &scan::ScanArgs,
|
||||
datastore: &Arc<Mutex<findings_store::FindingsStore>>,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
cli::{commands::scan, global},
|
||||
findings_store,
|
||||
findings_store::{FindingsStore, FindingsStoreMessage},
|
||||
github, gitlab,
|
||||
liquid_filters::register_all,
|
||||
matcher::MatcherStats,
|
||||
reporter::styles::Styles,
|
||||
|
|
@ -20,8 +21,8 @@ use crate::{
|
|||
scanner::{
|
||||
clone_or_update_git_repos, enumerate_filesystem_inputs, enumerate_github_repos,
|
||||
repos::{
|
||||
enumerate_gitlab_repos, fetch_confluence_pages, fetch_jira_issues, fetch_s3_objects,
|
||||
fetch_slack_messages,
|
||||
enumerate_gitlab_repos, fetch_confluence_pages, fetch_git_host_artifacts,
|
||||
fetch_jira_issues, fetch_s3_objects, fetch_slack_messages,
|
||||
},
|
||||
run_secret_validation, save_docker_images,
|
||||
summary::print_scan_summary,
|
||||
|
|
@ -76,7 +77,30 @@ pub async fn run_async_scan(
|
|||
repo_urls.sort();
|
||||
repo_urls.dedup();
|
||||
|
||||
// Add wiki repositories for each URL when requested
|
||||
if args.input_specifier_args.repo_artifacts {
|
||||
let mut wiki_urls = Vec::new();
|
||||
for url in &repo_urls {
|
||||
if let Some(w) = github::wiki_url(url) {
|
||||
wiki_urls.push(w);
|
||||
}
|
||||
if let Some(w) = gitlab::wiki_url(url) {
|
||||
wiki_urls.push(w);
|
||||
}
|
||||
}
|
||||
repo_urls.extend(wiki_urls);
|
||||
repo_urls.sort();
|
||||
repo_urls.dedup();
|
||||
}
|
||||
|
||||
let mut input_roots = clone_or_update_git_repos(args, global_args, &repo_urls, &datastore)?;
|
||||
|
||||
// Fetch issues, gists, and wikis if enabled
|
||||
if args.input_specifier_args.repo_artifacts {
|
||||
let repo_artifact_dirs =
|
||||
fetch_git_host_artifacts(&repo_urls, global_args, &datastore).await?;
|
||||
input_roots.extend(repo_artifact_dirs);
|
||||
}
|
||||
// Fetch Jira issues if requested
|
||||
let jira_dirs = fetch_jira_issues(args, global_args, &datastore).await?;
|
||||
input_roots.extend(jira_dirs);
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ fn run_skiplist(skip_regex: Vec<String>, skip_skipword: Vec<String>) -> Result<u
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 5.0,
|
||||
|
|
|
|||
|
|
@ -97,8 +97,9 @@ rules:
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 5.0,
|
||||
|
|
|
|||
|
|
@ -84,8 +84,9 @@ fn test_github_remote_scan() -> Result<()> {
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -82,8 +82,9 @@ fn test_gitlab_remote_scan() -> Result<()> {
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
@ -188,8 +189,9 @@ fn test_gitlab_remote_scan_no_history() -> Result<()> {
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::None,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -64,8 +64,9 @@ async fn test_redact_hashes_finding_values() -> Result<()> {
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -70,8 +70,9 @@ impl TestContext {
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
@ -166,8 +167,9 @@ async fn test_scan_slack_messages() -> Result<()> {
|
|||
docker_image: Vec::new(),
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -140,8 +140,9 @@ async fn test_validation_cache_and_depvars() -> Result<()> {
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
|
|
@ -83,8 +83,9 @@ impl TestContext {
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
@ -164,8 +165,9 @@ impl TestContext {
|
|||
// git clone / history options
|
||||
git_clone: GitCloneMode::Bare,
|
||||
git_history: GitHistoryMode::Full,
|
||||
scan_nested_repos: true,
|
||||
commit_metadata: true,
|
||||
repo_artifacts: false,
|
||||
scan_nested_repos: true,
|
||||
},
|
||||
content_filtering_args: ContentFilteringArgs {
|
||||
max_file_size_mb: 25.0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue