mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
Merge branch 'master' into PRWLR-7874-support-labels-on-jira-tickets
This commit is contained in:
@@ -157,21 +157,21 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Push README to Docker Hub (toniblyx)
|
||||
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: ${{ env.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}
|
||||
readme-filepath: ./README.md
|
||||
|
||||
- name: Push README to Docker Hub (prowlercloud)
|
||||
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}
|
||||
readme-filepath: ./README.md
|
||||
# - name: Push README to Docker Hub (toniblyx)
|
||||
# uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
|
||||
# with:
|
||||
# username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
# repository: ${{ env.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}
|
||||
# readme-filepath: ./README.md
|
||||
#
|
||||
# - name: Push README to Docker Hub (prowlercloud)
|
||||
# uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2
|
||||
# with:
|
||||
# username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
# repository: ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}
|
||||
# readme-filepath: ./README.md
|
||||
|
||||
dispatch-action:
|
||||
needs: container-build-push
|
||||
|
||||
@@ -273,7 +273,7 @@ python prowler-cli.py -v
|
||||
- **Prowler API**: A backend service, developed with Django REST Framework, responsible for running Prowler scans and storing the generated results.
|
||||
- **Prowler SDK**: A Python SDK designed to extend the functionality of the Prowler CLI for advanced capabilities.
|
||||
|
||||

|
||||

|
||||
|
||||
## Prowler CLI
|
||||
|
||||
|
||||
+2
-33
@@ -3,43 +3,11 @@
|
||||
All notable changes to the **Prowler SDK** are documented in this file.
|
||||
## [v5.12.0] (Prowler UNRELEASED)
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
---
|
||||
|
||||
## [v5.11.1] (Prowler UNRELEASED)
|
||||
|
||||
### Fixed
|
||||
|
||||
---
|
||||
|
||||
## [v5.12.0] (Prowler UNRELEASED)
|
||||
|
||||
### Added
|
||||
- Add more fields for the Jira ticket and handle custom fields errors [(#8601)](https://github.com/prowler-cloud/prowler/pull/8601)
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
---
|
||||
|
||||
## [v5.11.1] (Prowler UNRELEASED)
|
||||
|
||||
### Fixed
|
||||
|
||||
---
|
||||
|
||||
## [v5.12.0] (Prowler UNRELEASED)
|
||||
|
||||
### Added
|
||||
- Add more fields for the Jira ticket and handle custom fields errors [(#8601)](https://github.com/prowler-cloud/prowler/pull/8601)
|
||||
- Support labels on Jira tickets [(#8603)](https://github.com/prowler-cloud/prowler/pull/8603)
|
||||
- Add finding url and tenant info inside Jira tickets [(#8607)](https://github.com/prowler-cloud/prowler/pull/8607)
|
||||
- Add more fields for the Jira ticket and handle custom fields errors [(#8601)](https://github.com/prowler-cloud/prowler/pull/8601)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -50,6 +18,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
## [v5.11.1] (Prowler UNRELEASED)
|
||||
|
||||
### Fixed
|
||||
- `AdditionalUrls` field in CheckMetadata [(#8590)](https://github.com/prowler-cloud/prowler/pull/8590)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from dataclasses import asdict, dataclass, is_dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional, Set
|
||||
|
||||
from pydantic.v1 import BaseModel, ValidationError, validator
|
||||
from pydantic.v1 import BaseModel, Field, ValidationError, validator
|
||||
|
||||
from prowler.config.config import Provider
|
||||
from prowler.lib.check.compliance_models import Compliance
|
||||
@@ -85,6 +85,7 @@ class CheckMetadata(BaseModel):
|
||||
Risk (str): The risk associated with the check.
|
||||
RelatedUrl (str): The URL related to the check.
|
||||
Remediation (Remediation): The remediation steps for the check.
|
||||
AdditionalUrls (list[str]): Additional URLs related to the check. Defaults to an empty list.
|
||||
Categories (list[str]): The categories of the check.
|
||||
DependsOn (list[str]): The dependencies of the check.
|
||||
RelatedTo (list[str]): The related checks.
|
||||
@@ -97,13 +98,14 @@ class CheckMetadata(BaseModel):
|
||||
valid_severity(severity): Validator function to validate the severity of the check.
|
||||
valid_cli_command(remediation): Validator function to validate the CLI command is not an URL.
|
||||
valid_resource_type(resource_type): Validator function to validate the resource type is not empty.
|
||||
validate_additional_urls(additional_urls): Validator function to ensure AdditionalUrls contains no duplicates.
|
||||
"""
|
||||
|
||||
Provider: str
|
||||
CheckID: str
|
||||
CheckTitle: str
|
||||
CheckType: list[str]
|
||||
CheckAliases: list[str] = []
|
||||
CheckAliases: list[str] = Field(default_factory=list)
|
||||
ServiceName: str
|
||||
SubServiceName: str
|
||||
ResourceIdTemplate: str
|
||||
@@ -113,13 +115,14 @@ class CheckMetadata(BaseModel):
|
||||
Risk: str
|
||||
RelatedUrl: str
|
||||
Remediation: Remediation
|
||||
AdditionalUrls: list[str] = Field(default_factory=list)
|
||||
Categories: list[str]
|
||||
DependsOn: list[str]
|
||||
RelatedTo: list[str]
|
||||
Notes: str
|
||||
# We set the compliance to None to
|
||||
# store the compliance later if supplied
|
||||
Compliance: Optional[list[Any]] = []
|
||||
Compliance: Optional[list[Any]] = Field(default_factory=list)
|
||||
|
||||
@validator("Categories", each_item=True, pre=True, always=True)
|
||||
def valid_category(value):
|
||||
@@ -178,6 +181,19 @@ class CheckMetadata(BaseModel):
|
||||
|
||||
return check_id
|
||||
|
||||
@validator("AdditionalUrls", pre=True, always=True)
|
||||
def validate_additional_urls(cls, additional_urls):
|
||||
if not isinstance(additional_urls, list):
|
||||
raise ValueError("AdditionalUrls must be a list")
|
||||
|
||||
if any(not url or not url.strip() for url in additional_urls):
|
||||
raise ValueError("AdditionalUrls cannot contain empty items")
|
||||
|
||||
if len(additional_urls) != len(set(additional_urls)):
|
||||
raise ValueError("AdditionalUrls cannot contain duplicate items")
|
||||
|
||||
return additional_urls
|
||||
|
||||
@staticmethod
|
||||
def get_bulk(provider: str) -> dict[str, "CheckMetadata"]:
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pydantic.v1 import ValidationError
|
||||
|
||||
from prowler.lib.check.models import CheckMetadata
|
||||
from tests.lib.check.compliance_check_test import custom_compliance_metadata
|
||||
|
||||
@@ -325,3 +328,391 @@ class TestCheckMetada:
|
||||
|
||||
result = CheckMetadata.list(bulk_checks_metadata=bulk_metadata)
|
||||
assert result == set()
|
||||
|
||||
def test_additional_urls_valid_empty_list(self):
|
||||
"""Test AdditionalUrls with valid empty list (default)"""
|
||||
metadata = CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=[],
|
||||
Compliance=[],
|
||||
)
|
||||
assert metadata.AdditionalUrls == []
|
||||
|
||||
def test_additional_urls_valid_with_urls(self):
|
||||
"""Test AdditionalUrls with valid URLs"""
|
||||
valid_urls = [
|
||||
"https://example.com/doc1",
|
||||
"https://example.com/doc2",
|
||||
"https://aws.amazon.com/docs",
|
||||
]
|
||||
metadata = CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=valid_urls,
|
||||
Compliance=[],
|
||||
)
|
||||
assert metadata.AdditionalUrls == valid_urls
|
||||
|
||||
def test_additional_urls_invalid_not_list(self):
|
||||
"""Test AdditionalUrls with non-list value"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls="not_a_list",
|
||||
Compliance=[],
|
||||
)
|
||||
assert "AdditionalUrls must be a list" in str(exc_info.value)
|
||||
|
||||
def test_additional_urls_invalid_empty_items(self):
|
||||
"""Test AdditionalUrls with empty string items"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=["https://example.com", "", "https://example2.com"],
|
||||
Compliance=[],
|
||||
)
|
||||
assert "AdditionalUrls cannot contain empty items" in str(exc_info.value)
|
||||
|
||||
def test_additional_urls_invalid_whitespace_items(self):
|
||||
"""Test AdditionalUrls with whitespace-only items"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=["https://example.com", " ", "https://example2.com"],
|
||||
Compliance=[],
|
||||
)
|
||||
assert "AdditionalUrls cannot contain empty items" in str(exc_info.value)
|
||||
|
||||
def test_additional_urls_invalid_duplicates(self):
|
||||
"""Test AdditionalUrls with duplicate items"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check",
|
||||
CheckTitle="Test Check",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="url1",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=[
|
||||
"https://example.com",
|
||||
"https://example2.com",
|
||||
"https://example.com",
|
||||
],
|
||||
Compliance=[],
|
||||
)
|
||||
assert "AdditionalUrls cannot contain duplicate items" in str(exc_info.value)
|
||||
|
||||
def test_fields_with_explicit_empty_values(self):
|
||||
"""Test that RelatedUrl and AdditionalUrls can be set to explicit empty values"""
|
||||
metadata = CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check_empty_fields",
|
||||
CheckTitle="Test Check with Empty Fields",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="", # Explicit empty string
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=[], # Explicit empty list
|
||||
Compliance=[],
|
||||
)
|
||||
|
||||
# Assert that the fields are set to empty values
|
||||
assert metadata.RelatedUrl == ""
|
||||
assert metadata.AdditionalUrls == []
|
||||
|
||||
def test_fields_default_values(self):
|
||||
"""Test that RelatedUrl and AdditionalUrls use proper defaults when not provided"""
|
||||
metadata = CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check_defaults",
|
||||
CheckTitle="Test Check with Default Fields",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
# AdditionalUrls not provided - should default to empty list via default_factory
|
||||
Compliance=[],
|
||||
)
|
||||
|
||||
# Assert that the fields use their default values
|
||||
assert metadata.RelatedUrl == "" # Should default to empty string
|
||||
assert metadata.AdditionalUrls == [] # Should default to empty list
|
||||
|
||||
def test_related_url_none_fails(self):
|
||||
"""Test that setting RelatedUrl to None raises a ValidationError"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check_none_related_url",
|
||||
CheckTitle="Test Check with None RelatedUrl",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl=None, # This should fail
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=[],
|
||||
Compliance=[],
|
||||
)
|
||||
# Should contain a validation error for RelatedUrl
|
||||
assert "RelatedUrl" in str(exc_info.value)
|
||||
|
||||
def test_additional_urls_none_fails(self):
|
||||
"""Test that setting AdditionalUrls to None raises a ValidationError"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check_none_additional_urls",
|
||||
CheckTitle="Test Check with None AdditionalUrls",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="https://example.com",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls=None, # This should fail
|
||||
Compliance=[],
|
||||
)
|
||||
# Should contain the validation error we set in the validator
|
||||
assert "AdditionalUrls must be a list" in str(exc_info.value)
|
||||
|
||||
def test_additional_urls_invalid_type_fails(self):
|
||||
"""Test that setting AdditionalUrls to non-list value raises a ValidationError"""
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CheckMetadata(
|
||||
Provider="aws",
|
||||
CheckID="test_check_invalid_additional_urls",
|
||||
CheckTitle="Test Check with Invalid AdditionalUrls",
|
||||
CheckType=["type1"],
|
||||
ServiceName="test",
|
||||
SubServiceName="subservice1",
|
||||
ResourceIdTemplate="template1",
|
||||
Severity="high",
|
||||
ResourceType="resource1",
|
||||
Description="Description 1",
|
||||
Risk="risk1",
|
||||
RelatedUrl="https://example.com",
|
||||
Remediation={
|
||||
"Code": {
|
||||
"CLI": "cli1",
|
||||
"NativeIaC": "native1",
|
||||
"Other": "other1",
|
||||
"Terraform": "terraform1",
|
||||
},
|
||||
"Recommendation": {"Text": "text1", "Url": "url1"},
|
||||
},
|
||||
Categories=["categoryone"],
|
||||
DependsOn=["dependency1"],
|
||||
RelatedTo=["related1"],
|
||||
Notes="notes1",
|
||||
AdditionalUrls="not_a_list", # This should fail
|
||||
Compliance=[],
|
||||
)
|
||||
# Should contain the validation error we set in the validator
|
||||
assert "AdditionalUrls must be a list" in str(exc_info.value)
|
||||
|
||||
@@ -12,6 +12,9 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
- Markdown rendering in finding details page [(#8604)](https://github.com/prowler-cloud/prowler/pull/8604)
|
||||
|
||||
### 🐞 Fixed
|
||||
- Scan page shows NoProvidersAdded when no providers [(#8626)](https://github.com/prowler-cloud/prowler/pull/8626)
|
||||
|
||||
## [1.11.0] (Prowler v5.11.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -48,8 +48,8 @@ export const getComplianceOverviewMetadataInfo = async ({
|
||||
if (sort) url.searchParams.append("sort", sort);
|
||||
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
// Define filters to exclude
|
||||
if (key !== "filter[search]") {
|
||||
// Define filters to exclude and check for valid values
|
||||
if (key !== "filter[search]" && value && String(value).trim() !== "") {
|
||||
url.searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -91,12 +91,14 @@ export default async function Compliance({
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const metadataInfoData = await getComplianceOverviewMetadataInfo({
|
||||
query,
|
||||
filters: {
|
||||
"filter[scan_id]": selectedScanId,
|
||||
},
|
||||
});
|
||||
const metadataInfoData = selectedScanId
|
||||
? await getComplianceOverviewMetadataInfo({
|
||||
query,
|
||||
filters: {
|
||||
"filter[scan_id]": selectedScanId,
|
||||
},
|
||||
})
|
||||
: { data: { attributes: { regions: [] } } };
|
||||
|
||||
const uniqueRegions = metadataInfoData?.data?.attributes?.regions || [];
|
||||
|
||||
@@ -140,11 +142,15 @@ const SSRComplianceGrid = async ({
|
||||
// Extract query from filters
|
||||
const query = (filters["filter[search]"] as string) || "";
|
||||
|
||||
const compliancesData = await getCompliancesOverview({
|
||||
scanId,
|
||||
region: regionFilter,
|
||||
query,
|
||||
});
|
||||
// Only fetch compliance data if we have a valid scanId
|
||||
const compliancesData =
|
||||
scanId && scanId.trim() !== ""
|
||||
? await getCompliancesOverview({
|
||||
scanId,
|
||||
region: regionFilter,
|
||||
query,
|
||||
})
|
||||
: { data: [], errors: [] };
|
||||
|
||||
const type = compliancesData?.data?.type;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getLighthouseConfig } from "@/actions/lighthouse/lighthouse";
|
||||
import { LighthouseIcon } from "@/components/icons/Icons";
|
||||
import { Chat } from "@/components/lighthouse";
|
||||
@@ -7,6 +9,11 @@ export default async function AIChatbot() {
|
||||
const lighthouseConfig = await getLighthouseConfig();
|
||||
|
||||
const hasConfig = !!lighthouseConfig;
|
||||
|
||||
if (!hasConfig) {
|
||||
return redirect("/lighthouse/config");
|
||||
}
|
||||
|
||||
const isActive = lighthouseConfig.is_active ?? false;
|
||||
|
||||
return (
|
||||
|
||||
@@ -48,7 +48,8 @@ export default async function Scans({
|
||||
connected: provider.attributes.connection.connected,
|
||||
})) || [];
|
||||
|
||||
const thereIsNoProviders = !providersData?.data;
|
||||
const thereIsNoProviders =
|
||||
!providersData?.data || providersData.data.length === 0;
|
||||
|
||||
const thereIsNoProvidersConnected = providersData?.data?.every(
|
||||
(provider: ProviderProps) => !provider.attributes.connection.connected,
|
||||
|
||||
+37
-23
@@ -55,14 +55,16 @@ const validateAwsCredentialsCreate = (
|
||||
if (!data.aws_access_key_id) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Access Key ID is required when using access and secret key",
|
||||
message:
|
||||
"AWS Access Key ID is required when using access and secret key",
|
||||
path: ["aws_access_key_id"],
|
||||
});
|
||||
}
|
||||
if (!data.aws_secret_access_key) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Secret Access Key is required when using access and secret key",
|
||||
message:
|
||||
"AWS Secret Access Key is required when using access and secret key",
|
||||
path: ["aws_secret_access_key"],
|
||||
});
|
||||
}
|
||||
@@ -78,7 +80,8 @@ const validateAwsCredentialsEdit = (data: any, ctx: z.RefinementCtx) => {
|
||||
if (hasAccessKey && !hasSecretKey) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Secret Access Key is required when providing Access Key ID",
|
||||
message:
|
||||
"AWS Secret Access Key is required when providing Access Key ID",
|
||||
path: ["aws_secret_access_key"],
|
||||
});
|
||||
}
|
||||
@@ -86,7 +89,8 @@ const validateAwsCredentialsEdit = (data: any, ctx: z.RefinementCtx) => {
|
||||
if (hasSecretKey && !hasAccessKey) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Access Key ID is required when providing Secret Access Key",
|
||||
message:
|
||||
"AWS Access Key ID is required when providing Secret Access Key",
|
||||
path: ["aws_access_key_id"],
|
||||
});
|
||||
}
|
||||
@@ -99,8 +103,10 @@ const validateIamRole = (
|
||||
ctx: z.RefinementCtx,
|
||||
checkShowSection: boolean = true,
|
||||
) => {
|
||||
const shouldValidate = checkShowSection ? data.show_role_section === true : true;
|
||||
|
||||
const shouldValidate = checkShowSection
|
||||
? data.show_role_section === true
|
||||
: true;
|
||||
|
||||
if (shouldValidate && data.role_arn) {
|
||||
if (data.role_arn.trim() === "") {
|
||||
ctx.addIssue({
|
||||
@@ -160,9 +166,14 @@ export const s3IntegrationFormSchema = baseS3IntegrationSchema
|
||||
export const editS3IntegrationFormSchema = baseS3IntegrationSchema
|
||||
.extend({
|
||||
bucket_name: z.string().min(1, "Bucket name is required").optional(),
|
||||
output_directory: z.string().min(1, "Output directory is required").optional(),
|
||||
output_directory: z
|
||||
.string()
|
||||
.min(1, "Output directory is required")
|
||||
.optional(),
|
||||
providers: z.array(z.string()).optional(),
|
||||
credentials_type: z.enum(["aws-sdk-default", "access-secret-key"]).optional(),
|
||||
credentials_type: z
|
||||
.enum(["aws-sdk-default", "access-secret-key"])
|
||||
.optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
validateAwsCredentialsEdit(data, ctx);
|
||||
@@ -201,18 +212,21 @@ export const securityHubIntegrationFormSchema = baseSecurityHubIntegrationSchema
|
||||
}
|
||||
});
|
||||
|
||||
export const editSecurityHubIntegrationFormSchema = baseSecurityHubIntegrationSchema
|
||||
.extend({
|
||||
provider_id: z.string().optional(),
|
||||
send_only_fails: z.boolean().optional(),
|
||||
archive_previous_findings: z.boolean().optional(),
|
||||
use_custom_credentials: z.boolean().optional(),
|
||||
credentials_type: z.enum(["aws-sdk-default", "access-secret-key"]).optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.use_custom_credentials !== false) {
|
||||
validateAwsCredentialsEdit(data, ctx);
|
||||
}
|
||||
// Always validate role if role_arn is provided
|
||||
validateIamRole(data, ctx, false);
|
||||
});
|
||||
export const editSecurityHubIntegrationFormSchema =
|
||||
baseSecurityHubIntegrationSchema
|
||||
.extend({
|
||||
provider_id: z.string().optional(),
|
||||
send_only_fails: z.boolean().optional(),
|
||||
archive_previous_findings: z.boolean().optional(),
|
||||
use_custom_credentials: z.boolean().optional(),
|
||||
credentials_type: z
|
||||
.enum(["aws-sdk-default", "access-secret-key"])
|
||||
.optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.use_custom_credentials !== false) {
|
||||
validateAwsCredentialsEdit(data, ctx);
|
||||
}
|
||||
// Always validate role if role_arn is provided
|
||||
validateIamRole(data, ctx, false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user