diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index a6cbc6c9d4..c8b454ceaf 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -34,6 +34,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Missing `__init__.py` in `codebuild_project_uses_allowed_github_organizations` check preventing discovery by `--list-checks` [(#10584)](https://github.com/prowler-cloud/prowler/pull/10584) - Azure Key Vault checks emitting incorrect findings for keys, secrets, and vault logging [(#10332)](https://github.com/prowler-cloud/prowler/pull/10332) - `_enabled_regions` empty-set bug in `AwsProvider.generate_regional_clients` creating boto3 clients for all 36 AWS regions instead of the audited ones, causing random CI timeouts and slow test runs [(#10598)](https://github.com/prowler-cloud/prowler/pull/10598) +- Retrieve only the latest version from a package in AWS CodeArtifact [(#10243)](https://github.com/prowler-cloud/prowler/pull/10243) ### 🔐 Security diff --git a/prowler/providers/aws/services/codeartifact/codeartifact_service.py b/prowler/providers/aws/services/codeartifact/codeartifact_service.py index f3d312a531..1465092063 100644 --- a/prowler/providers/aws/services/codeartifact/codeartifact_service.py +++ b/prowler/providers/aws/services/codeartifact/codeartifact_service.py @@ -96,6 +96,7 @@ class CodeArtifact(AWSService): namespace=package_namespace, package=package_name, sortBy="PUBLISHED_TIME", + maxResults=1, ) ) else: @@ -111,6 +112,7 @@ class CodeArtifact(AWSService): format=package_format, package=package_name, sortBy="PUBLISHED_TIME", + maxResults=1, ) ) latest_version = "" diff --git a/tests/providers/aws/services/codeartifact/codeartifact_service_test.py b/tests/providers/aws/services/codeartifact/codeartifact_service_test.py index d1b3c6f9e6..99325dd2ea 100644 --- a/tests/providers/aws/services/codeartifact/codeartifact_service_test.py +++ b/tests/providers/aws/services/codeartifact/codeartifact_service_test.py @@ -54,6 +54,9 @@ def mock_make_api_call(self, operation_name, kwarg): } if operation_name == "ListPackageVersions": + assert ( + kwarg.get("maxResults") == 1 + ), "list_package_versions must pass maxResults=1 to avoid fetching all versions" return { "defaultDisplayVersion": "latest", "format": "pypi", @@ -204,3 +207,102 @@ class Test_CodeArtifact_Service: .latest_version.origin.origin_type == OriginInformationValues.INTERNAL ) + + +def mock_make_api_call_no_namespace(self, operation_name, kwarg): + """Mock for packages without a namespace to exercise the else branch""" + if operation_name == "ListRepositories": + return { + "repositories": [ + { + "name": "test-repository", + "administratorAccount": AWS_ACCOUNT_NUMBER, + "domainName": "test-domain", + "domainOwner": AWS_ACCOUNT_NUMBER, + "arn": TEST_REPOSITORY_ARN, + "description": "test description", + }, + ] + } + if operation_name == "ListPackages": + return { + "packages": [ + { + "format": "pypi", + "package": "test-package-no-ns", + "originConfiguration": { + "restrictions": { + "publish": "ALLOW", + "upstream": "BLOCK", + } + }, + }, + ], + } + + if operation_name == "ListPackageVersions": + assert ( + kwarg.get("maxResults") == 1 + ), "list_package_versions must pass maxResults=1 to avoid fetching all versions" + assert ( + "namespace" not in kwarg + ), "namespace should not be passed when package has no namespace" + return { + "defaultDisplayVersion": "1.0.0", + "format": "pypi", + "package": "test-package-no-ns", + "versions": [ + { + "version": "1.0.0", + "revision": "abc123", + "status": "Published", + "origin": { + "domainEntryPoint": { + "repositoryName": "test-repository", + "externalConnectionName": "", + }, + "originType": "EXTERNAL", + }, + }, + ], + } + + if operation_name == "ListTagsForResource": + return {"tags": []} + + return make_api_call(self, operation_name, kwarg) + + +@patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_no_namespace, +) +@patch( + "prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_CodeArtifact_Service_No_Namespace: + def test_list_packages_no_namespace(self): + codeartifact = CodeArtifact( + set_mocked_aws_provider([AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1]) + ) + + assert len(codeartifact.repositories[TEST_REPOSITORY_ARN].packages) == 1 + + package = codeartifact.repositories[TEST_REPOSITORY_ARN].packages[0] + assert package.name == "test-package-no-ns" + assert package.namespace is None + assert package.format == "pypi" + assert ( + package.origin_configuration.restrictions.publish == RestrictionValues.ALLOW + ) + assert ( + package.origin_configuration.restrictions.upstream + == RestrictionValues.BLOCK + ) + assert package.latest_version.version == "1.0.0" + assert package.latest_version.status == LatestPackageVersionStatus.Published + assert ( + package.latest_version.origin.origin_type + == OriginInformationValues.EXTERNAL + )