mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
fix(github): combine --repository and --organization flags for scan scoping (#10005)
Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
@@ -38,6 +38,7 @@ GitHub has deprecated Personal Access Tokens (classic) in favor of fine-grained
|
|||||||
|
|
||||||
4. **Configure Token Settings**
|
4. **Configure Token Settings**
|
||||||
- **Token name**: Give your token a descriptive name (e.g., "Prowler Security Scanner")
|
- **Token name**: Give your token a descriptive name (e.g., "Prowler Security Scanner")
|
||||||
|
- **Resource owner**: Select the account that owns the resources to scan — either a personal account or a specific organization
|
||||||
- **Expiration**: Set an appropriate expiration date (recommended: 90 days or less)
|
- **Expiration**: Set an appropriate expiration date (recommended: 90 days or less)
|
||||||
- **Repository access**: Choose "All repositories" or "Only select repositories" based on your needs
|
- **Repository access**: Choose "All repositories" or "Only select repositories" based on your needs
|
||||||
|
|
||||||
@@ -56,11 +57,11 @@ GitHub has deprecated Personal Access Tokens (classic) in favor of fine-grained
|
|||||||
- **Metadata**: Read-only access
|
- **Metadata**: Read-only access
|
||||||
- **Pull requests**: Read-only access
|
- **Pull requests**: Read-only access
|
||||||
|
|
||||||
- **Organization permissions:**
|
- **Organization permissions** (available when an organization is selected as Resource Owner):
|
||||||
- **Administration**: Read-only access
|
- **Administration**: Read-only access
|
||||||
- **Members**: Read-only access
|
- **Members**: Read-only access
|
||||||
|
|
||||||
- **Account permissions:**
|
- **Account permissions** (available when a personal account is selected as Resource Owner):
|
||||||
- **Email addresses**: Read-only access
|
- **Email addresses**: Read-only access
|
||||||
|
|
||||||
6. **Copy and Store the Token**
|
6. **Copy and Store the Token**
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ title: 'Getting Started with GitHub'
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
## Prowler CLI
|
## Prowler CLI
|
||||||
|
|
||||||
### Automatic Login Method Detection
|
### Authentication
|
||||||
|
|
||||||
If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:
|
If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:
|
||||||
|
|
||||||
@@ -68,15 +68,15 @@ Ensure the corresponding environment variables are set up before running Prowler
|
|||||||
</Note>
|
</Note>
|
||||||
For more details on how to set up authentication with GitHub, see [Authentication > GitHub](/user-guide/providers/github/authentication).
|
For more details on how to set up authentication with GitHub, see [Authentication > GitHub](/user-guide/providers/github/authentication).
|
||||||
|
|
||||||
### Personal Access Token (PAT)
|
#### Personal Access Token (PAT)
|
||||||
|
|
||||||
Use this method by providing your personal access token directly.
|
Use this method by providing a personal access token directly.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
prowler github --personal-access-token pat
|
prowler github --personal-access-token pat
|
||||||
```
|
```
|
||||||
|
|
||||||
### OAuth App Token
|
#### OAuth App Token
|
||||||
|
|
||||||
Authenticate using an OAuth app token.
|
Authenticate using an OAuth app token.
|
||||||
|
|
||||||
@@ -84,9 +84,62 @@ Authenticate using an OAuth app token.
|
|||||||
prowler github --oauth-app-token oauth_token
|
prowler github --oauth-app-token oauth_token
|
||||||
```
|
```
|
||||||
|
|
||||||
### GitHub App Credentials
|
#### GitHub App Credentials
|
||||||
|
|
||||||
Use GitHub App credentials by specifying the App ID and the private key path.
|
Use GitHub App credentials by specifying the App ID and the private key path.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
prowler github --github-app-id app_id --github-app-key-path app_key_path
|
prowler github --github-app-id app_id --github-app-key-path app_key_path
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Scan Scoping
|
||||||
|
|
||||||
|
By default, Prowler scans all repositories accessible to the authenticated user or organization. To limit the scan to specific repositories or organizations, use the following flags.
|
||||||
|
|
||||||
|
#### Scanning Specific Repositories
|
||||||
|
|
||||||
|
To restrict the scan to one or more repositories, use the `--repository` flag followed by the repository name(s) in `owner/repo-name` format:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --repository owner/repo-name
|
||||||
|
```
|
||||||
|
|
||||||
|
To scan multiple repositories, specify them as space-separated arguments:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --repository owner/repo-name-1 owner/repo-name-2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scanning Specific Organizations
|
||||||
|
|
||||||
|
To restrict the scan to one or more organizations or user accounts, use the `--organization` flag:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --organization my-organization
|
||||||
|
```
|
||||||
|
|
||||||
|
To scan multiple organizations, specify them as space-separated arguments:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --organization org-1 org-2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scanning Specific Repositories Within an Organization
|
||||||
|
|
||||||
|
To scan specific repositories within an organization, combine the `--organization` and `--repository` flags. The `--organization` flag qualifies unqualified repository names automatically:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --organization my-organization --repository my-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
This scans only `my-organization/my-repo`. Fully qualified repository names (`owner/repo-name`) are also supported alongside `--organization`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
prowler github --organization my-org --repository my-repo other-owner/other-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, `my-repo` is qualified as `my-org/my-repo`, while `other-owner/other-repo` is used as-is.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The `--repository` and `--organization` flags can be combined with any authentication method.
|
||||||
|
</Note>
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
All notable changes to the **Prowler SDK** are documented in this file.
|
All notable changes to the **Prowler SDK** are documented in this file.
|
||||||
|
|
||||||
|
## [5.18.2] (Prowler UNRELEASED)
|
||||||
|
|
||||||
|
### 🐞 Fixed
|
||||||
|
|
||||||
|
- `--repository` and `--organization` flags combined interaction in GitHub provider, qualifying unqualified repository names with organization [(#10001)](https://github.com/prowler-cloud/prowler/pull/10001)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [5.18.0] (Prowler v5.18.0)
|
## [5.18.0] (Prowler v5.18.0)
|
||||||
|
|
||||||
### 🚀 Added
|
### 🚀 Added
|
||||||
|
|||||||
@@ -121,15 +121,22 @@ class Repository(GithubService):
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
if self.provider.repositories:
|
if self.provider.repositories:
|
||||||
logger.info(
|
qualified_repos = []
|
||||||
f"Filtering for specific repositories: {self.provider.repositories}"
|
|
||||||
)
|
|
||||||
for repo_name in self.provider.repositories:
|
for repo_name in self.provider.repositories:
|
||||||
if not self._validate_repository_format(repo_name):
|
if self._validate_repository_format(repo_name):
|
||||||
|
qualified_repos.append(repo_name)
|
||||||
|
elif self.provider.organizations:
|
||||||
|
for org_name in self.provider.organizations:
|
||||||
|
qualified_repos.append(f"{org_name}/{repo_name}")
|
||||||
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Repository name '{repo_name}' should be in 'owner/repo-name' format. Skipping."
|
f"Repository name '{repo_name}' should be in 'owner/repo-name' format. Skipping."
|
||||||
)
|
)
|
||||||
continue
|
|
||||||
|
logger.info(
|
||||||
|
f"Filtering for specific repositories: {qualified_repos}"
|
||||||
|
)
|
||||||
|
for repo_name in qualified_repos:
|
||||||
try:
|
try:
|
||||||
repo = client.get_repo(repo_name)
|
repo = client.get_repo(repo_name)
|
||||||
self._process_repository(repo, repos)
|
self._process_repository(repo, repos)
|
||||||
@@ -138,7 +145,7 @@ class Repository(GithubService):
|
|||||||
error, "accessing repository", repo_name
|
error, "accessing repository", repo_name
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.provider.organizations:
|
elif self.provider.organizations:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Filtering for repositories in organizations: {self.provider.organizations}"
|
f"Filtering for repositories in organizations: {self.provider.organizations}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash
|
|||||||
- One entry per PR (can link multiple PRs for related changes)
|
- One entry per PR (can link multiple PRs for related changes)
|
||||||
- No period at the end
|
- No period at the end
|
||||||
- Do NOT start with redundant verbs (section header already provides the action)
|
- Do NOT start with redundant verbs (section header already provides the action)
|
||||||
|
- **CRITICAL: Preserve section order** — when adding a new section to the UNRELEASED block, insert it in the correct position relative to existing sections (Added → Changed → Deprecated → Removed → Fixed → Security). Never append a new section at the top or bottom without checking order
|
||||||
|
|
||||||
### Semantic Versioning Rules
|
### Semantic Versioning Rules
|
||||||
|
|
||||||
@@ -177,6 +178,13 @@ This maintains chronological order within each section (oldest at top, newest at
|
|||||||
### Bad Entries
|
### Bad Entries
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
|
# BAD - Wrong section order (Fixed before Added)
|
||||||
|
### 🐞 Fixed
|
||||||
|
- Some bug fix [(#123)](...)
|
||||||
|
|
||||||
|
### 🚀 Added
|
||||||
|
- Some new feature [(#456)](...)
|
||||||
|
|
||||||
- Fixed bug. # Too vague, has period
|
- Fixed bug. # Too vague, has period
|
||||||
- Added new feature for users # Missing PR link, redundant verb
|
- Added new feature for users # Missing PR link, redundant verb
|
||||||
- Add search bar [(#123)] # Redundant verb (section already says "Added")
|
- Add search bar [(#123)] # Redundant verb (section already says "Added")
|
||||||
|
|||||||
@@ -245,19 +245,61 @@ class Test_Repository_Scoping:
|
|||||||
self.mock_repo2.get_branch.side_effect = Exception("404 Not Found")
|
self.mock_repo2.get_branch.side_effect = Exception("404 Not Found")
|
||||||
self.mock_repo2.get_dependabot_alerts.side_effect = Exception("404 Not Found")
|
self.mock_repo2.get_dependabot_alerts.side_effect = Exception("404 Not Found")
|
||||||
|
|
||||||
def test_combined_repository_and_organization_scoping(self):
|
def test_qualified_repo_with_organization_skips_org_fetch(self):
|
||||||
"""Test that both repository and organization scoping can be used together"""
|
"""Test that a fully qualified repo with --organization does not fetch all org repos"""
|
||||||
provider = set_mocked_github_provider()
|
provider = set_mocked_github_provider()
|
||||||
provider.repositories = ["owner1/repo1"]
|
provider.repositories = ["owner1/repo1"]
|
||||||
provider.organizations = ["org2"]
|
provider.organizations = ["org2"]
|
||||||
|
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
# Repository lookup
|
|
||||||
mock_client.get_repo.return_value = self.mock_repo1
|
mock_client.get_repo.return_value = self.mock_repo1
|
||||||
# Organization lookup
|
|
||||||
mock_org = MagicMock()
|
with patch(
|
||||||
mock_org.get_repos.return_value = [self.mock_repo2]
|
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
||||||
mock_client.get_organization.return_value = mock_org
|
):
|
||||||
|
repository_service = Repository(provider)
|
||||||
|
repository_service.clients = [mock_client]
|
||||||
|
repository_service.provider = provider
|
||||||
|
|
||||||
|
repos = repository_service._list_repositories()
|
||||||
|
|
||||||
|
assert len(repos) == 1
|
||||||
|
assert 1 in repos
|
||||||
|
assert repos[1].name == "repo1"
|
||||||
|
mock_client.get_repo.assert_called_once_with("owner1/repo1")
|
||||||
|
mock_client.get_organization.assert_not_called()
|
||||||
|
|
||||||
|
def test_unqualified_repo_qualified_with_organization(self):
|
||||||
|
"""Test that an unqualified repo name is qualified with the organization"""
|
||||||
|
provider = set_mocked_github_provider()
|
||||||
|
provider.repositories = ["repo1"]
|
||||||
|
provider.organizations = ["owner1"]
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client.get_repo.return_value = self.mock_repo1
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
||||||
|
):
|
||||||
|
repository_service = Repository(provider)
|
||||||
|
repository_service.clients = [mock_client]
|
||||||
|
repository_service.provider = provider
|
||||||
|
|
||||||
|
repos = repository_service._list_repositories()
|
||||||
|
|
||||||
|
assert len(repos) == 1
|
||||||
|
assert 1 in repos
|
||||||
|
assert repos[1].name == "repo1"
|
||||||
|
mock_client.get_repo.assert_called_once_with("owner1/repo1")
|
||||||
|
|
||||||
|
def test_unqualified_repo_qualified_with_multiple_organizations(self):
|
||||||
|
"""Test that an unqualified repo is qualified with each organization"""
|
||||||
|
provider = set_mocked_github_provider()
|
||||||
|
provider.repositories = ["repo1"]
|
||||||
|
provider.organizations = ["org1", "org2"]
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client.get_repo.side_effect = [self.mock_repo1, self.mock_repo2]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
||||||
@@ -269,12 +311,56 @@ class Test_Repository_Scoping:
|
|||||||
repos = repository_service._list_repositories()
|
repos = repository_service._list_repositories()
|
||||||
|
|
||||||
assert len(repos) == 2
|
assert len(repos) == 2
|
||||||
assert 1 in repos
|
mock_client.get_repo.assert_any_call("org1/repo1")
|
||||||
assert 2 in repos
|
mock_client.get_repo.assert_any_call("org2/repo1")
|
||||||
assert repos[1].name == "repo1"
|
|
||||||
assert repos[2].name == "repo2"
|
def test_unqualified_repo_without_organization_is_skipped(self):
|
||||||
mock_client.get_repo.assert_called_once_with("owner1/repo1")
|
"""Test that an unqualified repo without --organization is skipped with a warning"""
|
||||||
mock_client.get_organization.assert_called_once_with("org2")
|
provider = set_mocked_github_provider()
|
||||||
|
provider.repositories = ["repo1"]
|
||||||
|
provider.organizations = []
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
||||||
|
):
|
||||||
|
repository_service = Repository(provider)
|
||||||
|
repository_service.clients = [mock_client]
|
||||||
|
repository_service.provider = provider
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"prowler.providers.github.services.repository.repository_service.logger"
|
||||||
|
) as mock_logger:
|
||||||
|
repos = repository_service._list_repositories()
|
||||||
|
|
||||||
|
assert len(repos) == 0
|
||||||
|
mock_logger.warning.assert_called_with(
|
||||||
|
"Repository name 'repo1' should be in 'owner/repo-name' format. Skipping."
|
||||||
|
)
|
||||||
|
mock_client.get_repo.assert_not_called()
|
||||||
|
|
||||||
|
def test_mixed_qualified_and_unqualified_repos_with_organization(self):
|
||||||
|
"""Test mix of qualified and unqualified repos with --organization"""
|
||||||
|
provider = set_mocked_github_provider()
|
||||||
|
provider.repositories = ["repo1", "owner2/repo2"]
|
||||||
|
provider.organizations = ["org1"]
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client.get_repo.side_effect = [self.mock_repo1, self.mock_repo2]
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"prowler.providers.github.services.repository.repository_service.GithubService.__init__"
|
||||||
|
):
|
||||||
|
repository_service = Repository(provider)
|
||||||
|
repository_service.clients = [mock_client]
|
||||||
|
repository_service.provider = provider
|
||||||
|
|
||||||
|
repos = repository_service._list_repositories()
|
||||||
|
|
||||||
|
assert len(repos) == 2
|
||||||
|
mock_client.get_repo.assert_any_call("org1/repo1")
|
||||||
|
mock_client.get_repo.assert_any_call("owner2/repo2")
|
||||||
|
|
||||||
|
|
||||||
class Test_Repository_Validation:
|
class Test_Repository_Validation:
|
||||||
|
|||||||
Reference in New Issue
Block a user