diff --git a/README.md b/README.md index d51f0b3..6873a6a 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,8 @@ KF_GITHUB_TOKEN="ghp_…" kingfisher scan --git-url https://github.com/org/priva ```bash kingfisher scan --gitlab-group my-group +# include repositories from all nested subgroups +kingfisher scan --gitlab-group my-group --gitlab-include-subgroups ``` ### Scan GitLab user @@ -402,6 +404,8 @@ kingfisher scan --git-url https://gitlab.com/group/project.git ```bash kingfisher gitlab repos list --group my-group +# include repositories from all nested subgroups +kingfisher gitlab repos list --group my-group --include-subgroups ``` ## Scanning Jira diff --git a/src/cli/commands/gitlab.rs b/src/cli/commands/gitlab.rs index e1bbdc3..8765c87 100644 --- a/src/cli/commands/gitlab.rs +++ b/src/cli/commands/gitlab.rs @@ -56,6 +56,10 @@ pub struct GitLabRepoSpecifiers { /// Filter by repository type #[arg(long, default_value_t = GitLabRepoType::All, alias = "gitlab-repo-type")] pub repo_type: GitLabRepoType, + + /// Include repositories from subgroups of the specified groups + #[arg(long, alias = "gitlab-include-subgroups")] + pub include_subgroups: bool, } impl GitLabRepoSpecifiers { diff --git a/src/cli/commands/inputs.rs b/src/cli/commands/inputs.rs index e6f9168..7836d79 100644 --- a/src/cli/commands/inputs.rs +++ b/src/cli/commands/inputs.rs @@ -89,6 +89,10 @@ pub struct InputSpecifierArgs { #[arg(long, default_value_t = GitLabRepoType::All)] pub gitlab_repo_type: GitLabRepoType, + /// Include projects from GitLab subgroups when scanning groups + #[arg(long, alias = "include-subgroups")] + pub gitlab_include_subgroups: bool, + /// Jira base URL (e.g. https://jira.example.com) #[arg(long, value_hint = ValueHint::Url, requires = "jql")] pub jira_url: Option, diff --git a/src/gitlab.rs b/src/gitlab.rs index e7df15e..80b7751 100644 --- a/src/gitlab.rs +++ b/src/gitlab.rs @@ -42,6 +42,7 @@ pub struct RepoSpecifiers { pub user: Vec, pub group: Vec, pub all_groups: bool, + pub include_subgroups: bool, pub repo_filter: RepoType, } @@ -137,6 +138,9 @@ pub async fn enumerate_repo_urls( if matches!(repo_specifiers.repo_filter, RepoType::Owner) { gp_builder.owned(true); } + if repo_specifiers.include_subgroups { + gp_builder.include_subgroups(true); + } let gp_ep = gp_builder.build()?; let projects: Vec = gp_ep.query(&client)?; @@ -162,10 +166,16 @@ pub async fn list_repositories( users: &[String], groups: &[String], all_groups: bool, + include_subgroups: bool, repo_filter: RepoType, ) -> Result<()> { - let repo_specifiers = - RepoSpecifiers { user: users.to_vec(), group: groups.to_vec(), all_groups, repo_filter }; + let repo_specifiers = RepoSpecifiers { + user: users.to_vec(), + group: groups.to_vec(), + all_groups, + include_subgroups, + repo_filter, + }; // Create a progress bar for displaying status let mut progress = if progress_enabled { diff --git a/src/main.rs b/src/main.rs index 3943b1e..2e9b6e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -245,6 +245,7 @@ async fn async_main(args: CommandLineArgs) -> Result<()> { &list_args.repo_specifiers.user, &list_args.repo_specifiers.group, list_args.repo_specifiers.all_groups, + list_args.repo_specifiers.include_subgroups, list_args.repo_specifiers.repo_type.into(), ) .await?; @@ -282,6 +283,7 @@ fn create_default_scan_args() -> cli::commands::scan::ScanArgs { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::All, + gitlab_include_subgroups: false, jira_url: None, jql: None, diff --git a/src/reporter/json_format.rs b/src/reporter/json_format.rs index 10d7aee..4020cee 100644 --- a/src/reporter/json_format.rs +++ b/src/reporter/json_format.rs @@ -84,6 +84,7 @@ mod tests { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::All, + gitlab_include_subgroups: false, // Jira options jira_url: None, jql: None, diff --git a/src/scanner/repos.rs b/src/scanner/repos.rs index a249f02..e5052c1 100644 --- a/src/scanner/repos.rs +++ b/src/scanner/repos.rs @@ -182,6 +182,7 @@ pub async fn enumerate_gitlab_repos( user: args.input_specifier_args.gitlab_user.clone(), group: args.input_specifier_args.gitlab_group.clone(), all_groups: args.input_specifier_args.all_gitlab_groups, + include_subgroups: args.input_specifier_args.gitlab_include_subgroups, repo_filter: args.input_specifier_args.gitlab_repo_type.into(), }; diff --git a/tests/int_dedup.rs b/tests/int_dedup.rs index 68b9663..090effa 100644 --- a/tests/int_dedup.rs +++ b/tests/int_dedup.rs @@ -78,6 +78,7 @@ rules: all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, diff --git a/tests/int_github.rs b/tests/int_github.rs index 0bae089..dbedcb5 100644 --- a/tests/int_github.rs +++ b/tests/int_github.rs @@ -65,6 +65,7 @@ fn test_github_remote_scan() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, diff --git a/tests/int_gitlab.rs b/tests/int_gitlab.rs index 7e48f60..5a72ce5 100644 --- a/tests/int_gitlab.rs +++ b/tests/int_gitlab.rs @@ -64,6 +64,7 @@ fn test_gitlab_remote_scan() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/")?, gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, @@ -169,6 +170,7 @@ fn test_gitlab_remote_scan_no_history() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/")?, gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, diff --git a/tests/int_redact.rs b/tests/int_redact.rs index 69a1061..6bd97c7 100644 --- a/tests/int_redact.rs +++ b/tests/int_redact.rs @@ -49,6 +49,7 @@ async fn test_redact_hashes_finding_values() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, confluence_url: None, diff --git a/tests/int_slack.rs b/tests/int_slack.rs index e94607f..86cea6f 100644 --- a/tests/int_slack.rs +++ b/tests/int_slack.rs @@ -55,6 +55,7 @@ impl TestContext { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, confluence_url: None, @@ -147,6 +148,7 @@ async fn test_scan_slack_messages() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, confluence_url: None, diff --git a/tests/int_validation_cache.rs b/tests/int_validation_cache.rs index 2caa10a..6e2cc6a 100644 --- a/tests/int_validation_cache.rs +++ b/tests/int_validation_cache.rs @@ -121,6 +121,7 @@ async fn test_validation_cache_and_depvars() -> Result<()> { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, diff --git a/tests/int_vulnerable_files.rs b/tests/int_vulnerable_files.rs index 2478170..31a74ac 100644 --- a/tests/int_vulnerable_files.rs +++ b/tests/int_vulnerable_files.rs @@ -64,6 +64,7 @@ impl TestContext { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None, @@ -142,6 +143,7 @@ impl TestContext { all_gitlab_groups: false, gitlab_api_url: Url::parse("https://gitlab.com/").unwrap(), gitlab_repo_type: GitLabRepoType::Owner, + gitlab_include_subgroups: false, jira_url: None, jql: None,