mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-31 21:27:28 +00:00
Compare commits
11 Commits
dependabot
...
iac-in-the
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5fe98b0d2 | ||
|
|
d47ff104d6 | ||
|
|
d09adb3edd | ||
|
|
f76847d653 | ||
|
|
06cf9ed0cc | ||
|
|
75390c0979 | ||
|
|
27f5c9591b | ||
|
|
dbff60576b | ||
|
|
ff22d198d0 | ||
|
|
5c2b867546 | ||
|
|
ae2200131f |
2
.env
2
.env
@@ -74,7 +74,7 @@ DJANGO_SETTINGS_MODULE=config.django.production
|
||||
DJANGO_LOGGING_FORMATTER=human_readable
|
||||
# Select one of [DEBUG|INFO|WARNING|ERROR|CRITICAL]
|
||||
# Applies to both Django and Celery Workers
|
||||
DJANGO_LOGGING_LEVEL=INFO
|
||||
DJANGO_LOGGING_LEVEL=DEBUG
|
||||
# Defaults to the maximum available based on CPU cores if not set.
|
||||
DJANGO_WORKERS=4
|
||||
# Token lifetime is in minutes
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- IaC (Infrastructure as Code) provider support for remote repositories [(#TBD)](https://github.com/prowler-cloud/prowler/pull/TBD)
|
||||
|
||||
---
|
||||
|
||||
## [1.13.0] (Prowler 5.12.0)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -36,6 +36,25 @@ RUN ARCH=$(uname -m) && \
|
||||
ln -s /opt/microsoft/powershell/7/pwsh /usr/bin/pwsh && \
|
||||
rm /tmp/powershell.tar.gz
|
||||
|
||||
# Install Trivy for IaC scanning
|
||||
ARG TRIVY_VERSION=0.66.0
|
||||
RUN ARCH=$(uname -m) && \
|
||||
if [ "$ARCH" = "x86_64" ]; then \
|
||||
TRIVY_ARCH="Linux-64bit" ; \
|
||||
elif [ "$ARCH" = "aarch64" ]; then \
|
||||
TRIVY_ARCH="Linux-ARM64" ; \
|
||||
else \
|
||||
echo "Unsupported architecture for Trivy: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
wget --progress=dot:giga "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" -O /tmp/trivy.tar.gz && \
|
||||
tar zxf /tmp/trivy.tar.gz -C /tmp && \
|
||||
mv /tmp/trivy /usr/local/bin/trivy && \
|
||||
chmod +x /usr/local/bin/trivy && \
|
||||
rm /tmp/trivy.tar.gz && \
|
||||
# Create trivy cache directory with proper permissions
|
||||
mkdir -p /tmp/.cache/trivy && \
|
||||
chmod 777 /tmp/.cache/trivy
|
||||
|
||||
# Add prowler user
|
||||
RUN addgroup --gid 1000 prowler && \
|
||||
adduser --uid 1000 --gid 1000 --disabled-password --gecos "" prowler
|
||||
|
||||
@@ -24,7 +24,7 @@ dependencies = [
|
||||
"drf-spectacular-jsonapi==0.5.1",
|
||||
"gunicorn==23.0.0",
|
||||
"lxml==5.3.2",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
|
||||
"prowler @ git+https://github.com/prowler-cloud/prowler.git@iac-in-the-app",
|
||||
"psycopg2-binary==2.9.9",
|
||||
"pytest-celery[redis] (>=1.0.1,<2.0.0)",
|
||||
"sentry-sdk[django] (>=2.20.0,<3.0.0)",
|
||||
|
||||
34
api/src/backend/api/migrations/0048_iac_provider.py
Normal file
34
api/src/backend/api/migrations/0048_iac_provider.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.1.10 on 2025-09-09 09:25
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import api.db_utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0047_remove_integration_unique_configuration_per_tenant"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="provider",
|
||||
name="provider",
|
||||
field=api.db_utils.ProviderEnumField(
|
||||
choices=[
|
||||
("aws", "AWS"),
|
||||
("azure", "Azure"),
|
||||
("gcp", "GCP"),
|
||||
("kubernetes", "Kubernetes"),
|
||||
("m365", "M365"),
|
||||
("github", "GitHub"),
|
||||
("iac", "IaC"),
|
||||
],
|
||||
default="aws",
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'iac';",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
]
|
||||
@@ -215,6 +215,7 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
KUBERNETES = "kubernetes", _("Kubernetes")
|
||||
M365 = "m365", _("M365")
|
||||
GITHUB = "github", _("GitHub")
|
||||
IAC = "iac", _("IaC")
|
||||
|
||||
@staticmethod
|
||||
def validate_aws_uid(value):
|
||||
@@ -285,6 +286,19 @@ class Provider(RowLevelSecurityProtectedModel):
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def validate_iac_uid(value):
|
||||
# Validate that it's a valid repository URL (git URL format)
|
||||
if not re.match(
|
||||
r"^(https?://|git@|ssh://)[^\s/]+[^\s]*\.git$|^(https?://)[^\s/]+[^\s]*$",
|
||||
value,
|
||||
):
|
||||
raise ModelValidationError(
|
||||
detail="IaC provider ID must be a valid repository URL (e.g., https://github.com/user/repo or https://github.com/user/repo.git).",
|
||||
code="iac-uid",
|
||||
pointer="/data/attributes/uid",
|
||||
)
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
inserted_at = models.DateTimeField(auto_now_add=True, editable=False)
|
||||
updated_at = models.DateTimeField(auto_now=True, editable=False)
|
||||
|
||||
@@ -601,6 +601,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -610,6 +611,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -633,6 +635,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1128,6 +1131,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -1137,6 +1141,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -1160,6 +1165,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1563,6 +1569,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -1572,6 +1579,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -1595,6 +1603,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -1996,6 +2005,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -2005,6 +2015,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2028,6 +2039,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -2417,6 +2429,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -2426,6 +2439,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -2449,6 +2463,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -3347,6 +3362,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -3356,6 +3372,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -3379,6 +3396,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -3514,6 +3532,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -3523,6 +3542,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -3546,6 +3566,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -3716,6 +3737,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -3725,6 +3747,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -3748,6 +3771,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -4430,6 +4454,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -4439,6 +4464,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider__in]
|
||||
schema:
|
||||
@@ -5044,6 +5070,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -5053,6 +5080,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5076,6 +5104,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5409,6 +5438,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -5418,6 +5448,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5441,6 +5472,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5675,6 +5707,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -5684,6 +5717,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5707,6 +5741,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -5947,6 +5982,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -5956,6 +5992,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -5979,6 +6016,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -6779,6 +6817,7 @@ paths:
|
||||
- azure
|
||||
- gcp
|
||||
- github
|
||||
- iac
|
||||
- kubernetes
|
||||
- m365
|
||||
description: |-
|
||||
@@ -6788,6 +6827,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
- in: query
|
||||
name: filter[provider_type__in]
|
||||
schema:
|
||||
@@ -6811,6 +6851,7 @@ paths:
|
||||
* `kubernetes` - Kubernetes
|
||||
* `m365` - M365
|
||||
* `github` - GitHub
|
||||
* `iac` - IaC
|
||||
explode: false
|
||||
style: form
|
||||
- in: query
|
||||
@@ -11767,6 +11808,17 @@ components:
|
||||
required:
|
||||
- github_app_id
|
||||
- github_app_key
|
||||
- type: object
|
||||
title: IaC Repository Credentials
|
||||
properties:
|
||||
repository_url:
|
||||
type: string
|
||||
description: Repository URL to scan for IaC files.
|
||||
access_token:
|
||||
type: string
|
||||
description: Optional access token for private repositories.
|
||||
required:
|
||||
- repository_url
|
||||
writeOnly: true
|
||||
required:
|
||||
- secret
|
||||
@@ -13540,6 +13592,17 @@ components:
|
||||
required:
|
||||
- github_app_id
|
||||
- github_app_key
|
||||
- type: object
|
||||
title: IaC Repository Credentials
|
||||
properties:
|
||||
repository_url:
|
||||
type: string
|
||||
description: Repository URL to scan for IaC files.
|
||||
access_token:
|
||||
type: string
|
||||
description: Optional access token for private repositories.
|
||||
required:
|
||||
- repository_url
|
||||
writeOnly: true
|
||||
required:
|
||||
- secret_type
|
||||
@@ -13788,6 +13851,17 @@ components:
|
||||
required:
|
||||
- github_app_id
|
||||
- github_app_key
|
||||
- type: object
|
||||
title: IaC Repository Credentials
|
||||
properties:
|
||||
repository_url:
|
||||
type: string
|
||||
description: Repository URL to scan for IaC files.
|
||||
access_token:
|
||||
type: string
|
||||
description: Optional access token for private repositories.
|
||||
required:
|
||||
- repository_url
|
||||
writeOnly: true
|
||||
required:
|
||||
- secret_type
|
||||
@@ -14052,6 +14126,17 @@ components:
|
||||
required:
|
||||
- github_app_id
|
||||
- github_app_key
|
||||
- type: object
|
||||
title: IaC Repository Credentials
|
||||
properties:
|
||||
repository_url:
|
||||
type: string
|
||||
description: Repository URL to scan for IaC files.
|
||||
access_token:
|
||||
type: string
|
||||
description: Optional access token for private repositories.
|
||||
required:
|
||||
- repository_url
|
||||
writeOnly: true
|
||||
required:
|
||||
- secret
|
||||
|
||||
@@ -991,6 +991,16 @@ class TestProviderViewSet:
|
||||
"uid": "a12345678901234567890123456789012345678",
|
||||
"alias": "Long Username",
|
||||
},
|
||||
{
|
||||
"provider": "iac",
|
||||
"uid": "https://github.com/user/repo.git",
|
||||
"alias": "Git Repo",
|
||||
},
|
||||
{
|
||||
"provider": "iac",
|
||||
"uid": "https://gitlab.com/user/project",
|
||||
"alias": "GitLab Repo",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -1140,6 +1150,33 @@ class TestProviderViewSet:
|
||||
"github-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "iac",
|
||||
"uid": "not-a-url",
|
||||
"alias": "test",
|
||||
},
|
||||
"iac-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "iac",
|
||||
"uid": "ftp://invalid-protocol.com/repo",
|
||||
"alias": "test",
|
||||
},
|
||||
"iac-uid",
|
||||
"uid",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "iac",
|
||||
"uid": "http://",
|
||||
"alias": "test",
|
||||
},
|
||||
"iac-uid",
|
||||
"uid",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ from prowler.providers.azure.azure_provider import AzureProvider
|
||||
from prowler.providers.common.models import Connection
|
||||
from prowler.providers.gcp.gcp_provider import GcpProvider
|
||||
from prowler.providers.github.github_provider import GithubProvider
|
||||
from prowler.providers.iac.iac_provider import IacProvider
|
||||
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
|
||||
from prowler.providers.m365.m365_provider import M365Provider
|
||||
|
||||
@@ -65,6 +66,7 @@ def return_prowler_provider(
|
||||
| AzureProvider
|
||||
| GcpProvider
|
||||
| GithubProvider
|
||||
| IacProvider
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
]:
|
||||
@@ -74,7 +76,7 @@ def return_prowler_provider(
|
||||
provider (Provider): The provider object containing the provider type and associated secrets.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | KubernetesProvider | M365Provider: The corresponding provider class.
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider: The corresponding provider class.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provider type specified in `provider.provider` is not supported.
|
||||
@@ -92,6 +94,8 @@ def return_prowler_provider(
|
||||
prowler_provider = M365Provider
|
||||
case Provider.ProviderChoices.GITHUB.value:
|
||||
prowler_provider = GithubProvider
|
||||
case Provider.ProviderChoices.IAC.value:
|
||||
prowler_provider = IacProvider
|
||||
case _:
|
||||
raise ValueError(f"Provider type {provider.provider} not supported")
|
||||
return prowler_provider
|
||||
@@ -128,6 +132,16 @@ def get_prowler_provider_kwargs(
|
||||
**prowler_provider_kwargs,
|
||||
"organizations": [provider.uid],
|
||||
}
|
||||
elif provider.provider == Provider.ProviderChoices.IAC.value:
|
||||
# For IaC provider, uid contains the repository URL
|
||||
# Extract the access token if present in the secret
|
||||
prowler_provider_kwargs = {
|
||||
"scan_repository_url": provider.uid,
|
||||
}
|
||||
if "access_token" in provider.secret.secret:
|
||||
prowler_provider_kwargs["oauth_app_token"] = provider.secret.secret[
|
||||
"access_token"
|
||||
]
|
||||
|
||||
if mutelist_processor:
|
||||
mutelist_content = mutelist_processor.configuration.get("Mutelist", {})
|
||||
@@ -145,6 +159,7 @@ def initialize_prowler_provider(
|
||||
| AzureProvider
|
||||
| GcpProvider
|
||||
| GithubProvider
|
||||
| IacProvider
|
||||
| KubernetesProvider
|
||||
| M365Provider
|
||||
):
|
||||
@@ -155,8 +170,8 @@ def initialize_prowler_provider(
|
||||
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
|
||||
|
||||
Returns:
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | KubernetesProvider | M365Provider: An instance of the corresponding provider class
|
||||
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `KubernetesProvider` or `M365Provider`) initialized with the
|
||||
AwsProvider | AzureProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider: An instance of the corresponding provider class
|
||||
(`AwsProvider`, `AzureProvider`, `GcpProvider`, `GithubProvider`, `IacProvider`, `KubernetesProvider` or `M365Provider`) initialized with the
|
||||
provider's secrets.
|
||||
"""
|
||||
prowler_provider = return_prowler_provider(provider)
|
||||
@@ -180,9 +195,23 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
|
||||
except Provider.secret.RelatedObjectDoesNotExist as secret_error:
|
||||
return Connection(is_connected=False, error=secret_error)
|
||||
|
||||
return prowler_provider.test_connection(
|
||||
**prowler_provider_kwargs, provider_id=provider.uid, raise_on_exception=False
|
||||
)
|
||||
# For IaC provider, construct the kwargs properly for test_connection
|
||||
if provider.provider == Provider.ProviderChoices.IAC.value:
|
||||
# Don't pass repository_url from secret, use scan_repository_url with the UID
|
||||
iac_test_kwargs = {
|
||||
"scan_repository_url": provider.uid,
|
||||
"raise_on_exception": False,
|
||||
}
|
||||
# Add access_token if present in the secret
|
||||
if "access_token" in prowler_provider_kwargs:
|
||||
iac_test_kwargs["access_token"] = prowler_provider_kwargs["access_token"]
|
||||
return prowler_provider.test_connection(**iac_test_kwargs)
|
||||
else:
|
||||
return prowler_provider.test_connection(
|
||||
**prowler_provider_kwargs,
|
||||
provider_id=provider.uid,
|
||||
raise_on_exception=False,
|
||||
)
|
||||
|
||||
|
||||
def prowler_integration_connection_test(integration: Integration) -> Connection:
|
||||
|
||||
@@ -213,6 +213,21 @@ from rest_framework_json_api import serializers
|
||||
},
|
||||
"required": ["github_app_id", "github_app_key"],
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "IaC Repository Credentials",
|
||||
"properties": {
|
||||
"repository_url": {
|
||||
"type": "string",
|
||||
"description": "Repository URL to scan for IaC files.",
|
||||
},
|
||||
"access_token": {
|
||||
"type": "string",
|
||||
"description": "Optional access token for private repositories.",
|
||||
},
|
||||
},
|
||||
"required": ["repository_url"],
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1223,6 +1223,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
|
||||
serializer = GCPProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.GITHUB.value:
|
||||
serializer = GithubProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.IAC.value:
|
||||
serializer = IacProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.KUBERNETES.value:
|
||||
serializer = KubernetesProviderSecret(data=secret)
|
||||
elif provider_type == Provider.ProviderChoices.M365.value:
|
||||
@@ -1312,6 +1314,14 @@ class GithubProviderSecret(serializers.Serializer):
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class IacProviderSecret(serializers.Serializer):
|
||||
repository_url = serializers.CharField()
|
||||
access_token = serializers.CharField(required=False)
|
||||
|
||||
class Meta:
|
||||
resource_name = "provider-secrets"
|
||||
|
||||
|
||||
class AWSRoleAssumptionProviderSecret(serializers.Serializer):
|
||||
role_arn = serializers.CharField()
|
||||
external_id = serializers.CharField()
|
||||
|
||||
@@ -100,6 +100,10 @@ COMPLIANCE_CLASS_MAP = {
|
||||
"github": [
|
||||
(lambda name: name.startswith("cis_"), GithubCIS),
|
||||
],
|
||||
"iac": [
|
||||
# IaC provider doesn't have specific compliance frameworks yet
|
||||
# Trivy handles its own compliance checks
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -309,10 +309,17 @@ class Finding(BaseModel):
|
||||
output_data["auth_method"] = provider.auth_method
|
||||
output_data["account_uid"] = "iac"
|
||||
output_data["account_name"] = "iac"
|
||||
output_data["resource_name"] = check_output.resource_name
|
||||
output_data["resource_uid"] = check_output.resource_name
|
||||
output_data["region"] = check_output.resource_line_range
|
||||
output_data["resource_line_range"] = check_output.resource_line_range
|
||||
output_data["resource_name"] = getattr(
|
||||
check_output, "resource_name", ""
|
||||
)
|
||||
output_data["resource_uid"] = getattr(check_output, "resource_name", "")
|
||||
# For IaC, resource_line_range only exists on CheckReportIAC, not on Finding objects
|
||||
output_data["region"] = getattr(
|
||||
check_output, "resource_line_range", "file"
|
||||
)
|
||||
output_data["resource_line_range"] = getattr(
|
||||
check_output, "resource_line_range", ""
|
||||
)
|
||||
output_data["framework"] = check_output.check_metadata.ServiceName
|
||||
|
||||
# check_output Unique ID
|
||||
@@ -385,6 +392,10 @@ class Finding(BaseModel):
|
||||
finding.subscription = list(provider.identity.subscriptions.keys())[0]
|
||||
elif provider.type == "gcp":
|
||||
finding.project_id = list(provider.projects.keys())[0]
|
||||
elif provider.type == "iac":
|
||||
# For IaC, we don't have resource_line_range in the Finding model
|
||||
# It would need to be extracted from the resource metadata if needed
|
||||
finding.resource_line_range = "" # Set empty for compatibility
|
||||
|
||||
finding.check_metadata = CheckMetadata(
|
||||
Provider=finding.check_metadata["provider"],
|
||||
|
||||
@@ -93,12 +93,22 @@ class Scan:
|
||||
# Load bulk compliance frameworks
|
||||
self._bulk_compliance_frameworks = Compliance.get_bulk(provider.type)
|
||||
|
||||
# Get bulk checks metadata for the provider
|
||||
self._bulk_checks_metadata = CheckMetadata.get_bulk(provider.type)
|
||||
# Complete checks metadata with the compliance framework specification
|
||||
self._bulk_checks_metadata = update_checks_metadata_with_compliance(
|
||||
self._bulk_compliance_frameworks, self._bulk_checks_metadata
|
||||
)
|
||||
# Special setup for IaC provider - override inputs to work with traditional flow
|
||||
if provider.type == "iac":
|
||||
# IaC doesn't use traditional Prowler checks, so clear all input parameters
|
||||
# to avoid validation errors and let it flow through the normal logic
|
||||
checks = None
|
||||
services = None
|
||||
excluded_checks = None
|
||||
excluded_services = None
|
||||
self._bulk_checks_metadata = {}
|
||||
else:
|
||||
# Get bulk checks metadata for the provider
|
||||
self._bulk_checks_metadata = CheckMetadata.get_bulk(provider.type)
|
||||
# Complete checks metadata with the compliance framework specification
|
||||
self._bulk_checks_metadata = update_checks_metadata_with_compliance(
|
||||
self._bulk_compliance_frameworks, self._bulk_checks_metadata
|
||||
)
|
||||
|
||||
# Create a list of valid categories
|
||||
valid_categories = set()
|
||||
@@ -148,19 +158,22 @@ class Scan:
|
||||
)
|
||||
|
||||
# Load checks to execute
|
||||
self._checks_to_execute = sorted(
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=self._bulk_checks_metadata,
|
||||
bulk_compliance_frameworks=self._bulk_compliance_frameworks,
|
||||
check_list=checks,
|
||||
service_list=services,
|
||||
compliance_frameworks=compliances,
|
||||
categories=categories,
|
||||
severities=severities,
|
||||
provider=provider.type,
|
||||
checks_file=None,
|
||||
if provider.type == "iac":
|
||||
self._checks_to_execute = ["iac_scan"] # Dummy check name for IaC
|
||||
else:
|
||||
self._checks_to_execute = sorted(
|
||||
load_checks_to_execute(
|
||||
bulk_checks_metadata=self._bulk_checks_metadata,
|
||||
bulk_compliance_frameworks=self._bulk_compliance_frameworks,
|
||||
check_list=checks,
|
||||
service_list=services,
|
||||
compliance_frameworks=compliances,
|
||||
categories=categories,
|
||||
severities=severities,
|
||||
provider=provider.type,
|
||||
checks_file=None,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Exclude checks
|
||||
if excluded_checks:
|
||||
@@ -184,9 +197,13 @@ class Scan:
|
||||
|
||||
self._number_of_checks_to_execute = len(self._checks_to_execute)
|
||||
|
||||
service_checks_to_execute = get_service_checks_to_execute(
|
||||
self._checks_to_execute
|
||||
)
|
||||
# Set up service-based checks tracking
|
||||
if provider.type == "iac":
|
||||
service_checks_to_execute = {"iac": set(["iac_scan"])}
|
||||
else:
|
||||
service_checks_to_execute = get_service_checks_to_execute(
|
||||
self._checks_to_execute
|
||||
)
|
||||
service_checks_completed = dict()
|
||||
|
||||
self._service_checks_to_execute = service_checks_to_execute
|
||||
@@ -245,6 +262,9 @@ class Scan:
|
||||
Exception: If any other error occurs during the execution of a check.
|
||||
"""
|
||||
try:
|
||||
# Initialize check_name for error handling
|
||||
check_name = None
|
||||
|
||||
# Using SimpleNamespace to create a mocked object
|
||||
arguments = SimpleNamespace()
|
||||
|
||||
@@ -266,6 +286,65 @@ class Scan:
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
|
||||
# Special handling for IaC provider
|
||||
if self._provider.type == "iac":
|
||||
# IaC provider doesn't use regular checks, it runs Trivy directly
|
||||
from prowler.providers.iac.iac_provider import IacProvider
|
||||
|
||||
if isinstance(self._provider, IacProvider):
|
||||
logger.info("Running IaC scan with Trivy...")
|
||||
# Run the IaC scan
|
||||
iac_reports = self._provider.run()
|
||||
|
||||
# Convert IaC reports to Finding objects
|
||||
findings = []
|
||||
from datetime import timezone
|
||||
|
||||
from prowler.lib.outputs.common import Status
|
||||
from prowler.lib.outputs.finding import Finding
|
||||
|
||||
for report in iac_reports:
|
||||
# Generate unique UID for the finding
|
||||
finding_uid = f"{report.check_metadata.CheckID}-{report.resource_name}-{report.resource_line_range}"
|
||||
|
||||
# Convert status string to Status enum
|
||||
status_enum = (
|
||||
Status.FAIL if report.status == "FAIL" else Status.PASS
|
||||
)
|
||||
if report.muted:
|
||||
status_enum = Status.MUTED
|
||||
|
||||
finding = Finding(
|
||||
auth_method="Repository", # IaC uses repository as auth method
|
||||
timestamp=datetime.datetime.now(timezone.utc),
|
||||
account_uid=self._provider.scan_repository_url or "local",
|
||||
account_name="IaC Repository",
|
||||
metadata=report.check_metadata, # Pass the CheckMetadata object directly
|
||||
uid=finding_uid,
|
||||
status=status_enum,
|
||||
status_extended=report.status_extended,
|
||||
muted=report.muted,
|
||||
resource_uid=report.resource_name, # For IaC, the file path is the UID
|
||||
resource_metadata=report.resource, # The raw finding dict
|
||||
resource_name=report.resource_name,
|
||||
resource_details=report.resource_details,
|
||||
resource_tags={}, # IaC doesn't have resource tags
|
||||
region="global", # IaC doesn't have regions
|
||||
compliance={}, # IaC doesn't have compliance mappings yet
|
||||
raw=report.resource, # The raw finding dict
|
||||
)
|
||||
findings.append(finding)
|
||||
|
||||
# Update progress and yield findings
|
||||
self._number_of_checks_completed = 1
|
||||
self._number_of_checks_to_execute = 1
|
||||
yield (100.0, findings)
|
||||
|
||||
# Calculate duration
|
||||
end_time = datetime.datetime.now()
|
||||
self._duration = int((end_time - start_time).total_seconds())
|
||||
return
|
||||
|
||||
for check_name in checks_to_execute:
|
||||
try:
|
||||
# Recover service from check name
|
||||
@@ -341,9 +420,14 @@ class Scan:
|
||||
# Update the scan duration when all checks are completed
|
||||
self._duration = int((datetime.datetime.now() - start_time).total_seconds())
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
if check_name:
|
||||
logger.error(
|
||||
f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"Scan error - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def get_completed_services(self) -> set[str]:
|
||||
"""
|
||||
|
||||
@@ -242,20 +242,37 @@ class IacProvider(Provider):
|
||||
logger.info(
|
||||
f"Cloning repository {original_url} into {temporary_directory}..."
|
||||
)
|
||||
with alive_bar(
|
||||
ctrl_c=False,
|
||||
bar="blocks",
|
||||
spinner="classic",
|
||||
stats=False,
|
||||
enrich_print=False,
|
||||
) as bar:
|
||||
try:
|
||||
bar.title = f"-> Cloning {original_url}..."
|
||||
|
||||
# Check if we're in an environment with a TTY
|
||||
# Celery workers and other non-interactive environments don't have TTY
|
||||
# and cannot use the alive_bar
|
||||
try:
|
||||
if sys.stdout.isatty():
|
||||
with alive_bar(
|
||||
ctrl_c=False,
|
||||
bar="blocks",
|
||||
spinner="classic",
|
||||
stats=False,
|
||||
enrich_print=False,
|
||||
) as bar:
|
||||
try:
|
||||
bar.title = f"-> Cloning {original_url}..."
|
||||
porcelain.clone(repository_url, temporary_directory, depth=1)
|
||||
bar.title = "-> Repository cloned successfully!"
|
||||
except Exception as clone_error:
|
||||
bar.title = "-> Cloning failed!"
|
||||
raise clone_error
|
||||
else:
|
||||
# No TTY, just clone without progress bar
|
||||
logger.info(f"Cloning {original_url}...")
|
||||
porcelain.clone(repository_url, temporary_directory, depth=1)
|
||||
bar.title = "-> Repository cloned successfully!"
|
||||
except Exception as clone_error:
|
||||
bar.title = "-> Cloning failed!"
|
||||
raise clone_error
|
||||
logger.info("Repository cloned successfully!")
|
||||
except (AttributeError, OSError):
|
||||
# Fallback if isatty() check fails
|
||||
logger.info(f"Cloning {original_url}...")
|
||||
porcelain.clone(repository_url, temporary_directory, depth=1)
|
||||
logger.info("Repository cloned successfully!")
|
||||
|
||||
return temporary_directory
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
@@ -302,25 +319,47 @@ class IacProvider(Provider):
|
||||
]
|
||||
if exclude_path:
|
||||
trivy_command.extend(["--skip-dirs", ",".join(exclude_path)])
|
||||
with alive_bar(
|
||||
ctrl_c=False,
|
||||
bar="blocks",
|
||||
spinner="classic",
|
||||
stats=False,
|
||||
enrich_print=False,
|
||||
) as bar:
|
||||
try:
|
||||
bar.title = f"-> Running IaC scan on {directory} ..."
|
||||
# Run Trivy with JSON output
|
||||
|
||||
# Check if we're in an environment with a TTY
|
||||
try:
|
||||
if sys.stdout.isatty():
|
||||
with alive_bar(
|
||||
ctrl_c=False,
|
||||
bar="blocks",
|
||||
spinner="classic",
|
||||
stats=False,
|
||||
enrich_print=False,
|
||||
) as bar:
|
||||
try:
|
||||
bar.title = f"-> Running IaC scan on {directory} ..."
|
||||
# Run Trivy with JSON output
|
||||
process = subprocess.run(
|
||||
trivy_command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
bar.title = "-> Scan completed!"
|
||||
except Exception as error:
|
||||
bar.title = "-> Scan failed!"
|
||||
raise error
|
||||
else:
|
||||
# No TTY, just run without progress bar
|
||||
logger.info(f"Running Trivy scan on {directory}...")
|
||||
process = subprocess.run(
|
||||
trivy_command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
bar.title = "-> Scan completed!"
|
||||
except Exception as error:
|
||||
bar.title = "-> Scan failed!"
|
||||
raise error
|
||||
logger.info("Trivy scan completed!")
|
||||
except (AttributeError, OSError):
|
||||
# Fallback if isatty() check fails
|
||||
logger.info(f"Running Trivy scan on {directory}...")
|
||||
process = subprocess.run(
|
||||
trivy_command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
logger.info("Trivy scan completed!")
|
||||
# Log Trivy's stderr output with preserved log levels
|
||||
if process.stderr:
|
||||
for line in process.stderr.strip().split("\n"):
|
||||
@@ -434,3 +473,99 @@ class IacProvider(Provider):
|
||||
)
|
||||
|
||||
print_boxes(report_lines, report_title)
|
||||
|
||||
@staticmethod
|
||||
def test_connection(
|
||||
scan_repository_url: str = None,
|
||||
oauth_app_token: str = None,
|
||||
access_token: str = None,
|
||||
raise_on_exception: bool = True,
|
||||
provider_id: str = None,
|
||||
) -> "Connection":
|
||||
"""Test connection to IaC repository.
|
||||
|
||||
Test the connection to the IaC repository using the provided credentials.
|
||||
|
||||
Args:
|
||||
scan_repository_url (str): Repository URL to scan.
|
||||
oauth_app_token (str): OAuth App token for authentication.
|
||||
access_token (str): Access token for authentication (alias for oauth_app_token).
|
||||
raise_on_exception (bool): Flag indicating whether to raise an exception if the connection fails.
|
||||
provider_id (str): The provider ID, in this case it's the repository URL.
|
||||
|
||||
Returns:
|
||||
Connection: Connection object with success status or error information.
|
||||
|
||||
Raises:
|
||||
Exception: If failed to test the connection to the repository.
|
||||
|
||||
Examples:
|
||||
>>> IacProvider.test_connection(scan_repository_url="https://github.com/user/repo")
|
||||
Connection(is_connected=True)
|
||||
"""
|
||||
from prowler.providers.common.models import Connection
|
||||
|
||||
try:
|
||||
# If provider_id is provided and scan_repository_url is not, use provider_id as the repository URL
|
||||
if provider_id and not scan_repository_url:
|
||||
scan_repository_url = provider_id
|
||||
|
||||
# Handle both oauth_app_token and access_token parameters
|
||||
if access_token and not oauth_app_token:
|
||||
oauth_app_token = access_token
|
||||
|
||||
if not scan_repository_url:
|
||||
return Connection(
|
||||
is_connected=False,
|
||||
error="Repository URL is required"
|
||||
)
|
||||
|
||||
# Try to clone the repository to test the connection
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
try:
|
||||
if oauth_app_token:
|
||||
# If token is provided, use it for authentication
|
||||
# Extract the domain and path from the URL
|
||||
import re
|
||||
url_pattern = r"(https?://)([^/]+)/(.+)"
|
||||
match = re.match(url_pattern, scan_repository_url)
|
||||
if match:
|
||||
protocol, domain, path = match.groups()
|
||||
# Construct URL with token
|
||||
auth_url = f"{protocol}x-access-token:{oauth_app_token}@{domain}/{path}"
|
||||
else:
|
||||
auth_url = scan_repository_url
|
||||
else:
|
||||
# Public repository
|
||||
auth_url = scan_repository_url
|
||||
|
||||
# Use dulwich to test the connection
|
||||
porcelain.ls_remote(auth_url)
|
||||
|
||||
return Connection(is_connected=True)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "authentication" in error_msg.lower() or "401" in error_msg:
|
||||
return Connection(
|
||||
is_connected=False,
|
||||
error="Authentication failed. Please check your access token."
|
||||
)
|
||||
elif "404" in error_msg or "not found" in error_msg.lower():
|
||||
return Connection(
|
||||
is_connected=False,
|
||||
error="Repository not found or not accessible."
|
||||
)
|
||||
else:
|
||||
return Connection(
|
||||
is_connected=False,
|
||||
error=f"Failed to connect to repository: {error_msg}"
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
if raise_on_exception:
|
||||
raise
|
||||
return Connection(
|
||||
is_connected=False,
|
||||
error=f"Unexpected error testing connection: {str(error)}"
|
||||
)
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- IaC (Infrastructure as Code) provider support for scanning remote repositories [(#TBD)](https://github.com/prowler-cloud/prowler/pull/TBD)
|
||||
|
||||
---
|
||||
|
||||
## [1.12.1] (Prowler v5.12.1)
|
||||
|
||||
### 🚀 Added
|
||||
@@ -14,6 +22,8 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- Field-level email validation message [(#8698)] (https://github.com/prowler-cloud/prowler/pull/8698)
|
||||
- POST method on auth form [(#8699)] (https://github.com/prowler-cloud/prowler/pull/8699)
|
||||
|
||||
---
|
||||
|
||||
## [1.12.0] (Prowler v5.12.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
@@ -62,3 +63,12 @@ export const CustomProviderInputGitHub = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomProviderInputIac = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<IacProviderBadge width={25} height={25} />
|
||||
<p className="text-sm">Infrastructure as Code</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
CustomProviderInputAzure,
|
||||
CustomProviderInputGCP,
|
||||
CustomProviderInputGitHub,
|
||||
CustomProviderInputIac,
|
||||
CustomProviderInputKubernetes,
|
||||
CustomProviderInputM365,
|
||||
} from "./custom-provider-inputs";
|
||||
@@ -43,6 +44,10 @@ const providerDisplayData: Record<
|
||||
label: "GitHub",
|
||||
component: <CustomProviderInputGitHub />,
|
||||
},
|
||||
iac: {
|
||||
label: "Infrastructure as Code",
|
||||
component: <CustomProviderInputIac />,
|
||||
},
|
||||
};
|
||||
|
||||
const dataInputsProvider = PROVIDER_TYPES.map((providerType) => ({
|
||||
|
||||
44
ui/components/icons/providers-badge/iac-provider-badge.tsx
Normal file
44
ui/components/icons/providers-badge/iac-provider-badge.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
export const IacProviderBadge: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M13 21L17 3"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7 8L3 12L7 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17 8L21 12L17 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -2,5 +2,6 @@ export * from "./aws-provider-badge";
|
||||
export * from "./azure-provider-badge";
|
||||
export * from "./gcp-provider-badge";
|
||||
export * from "./github-provider-badge";
|
||||
export * from "./iac-provider-badge";
|
||||
export * from "./ks8-provider-badge";
|
||||
export * from "./m365-provider-badge";
|
||||
|
||||
@@ -49,6 +49,7 @@ export const ProvidersOverview = ({
|
||||
gcp: "GCP",
|
||||
kubernetes: "Kubernetes",
|
||||
github: "GitHub",
|
||||
iac: "IaC",
|
||||
};
|
||||
|
||||
const providers = PROVIDER_TYPES.map((providerType) => ({
|
||||
|
||||
@@ -15,6 +15,7 @@ const providerTypeLabels: Record<ProviderType, string> = {
|
||||
m365: "Microsoft 365",
|
||||
kubernetes: "Kubernetes",
|
||||
github: "GitHub",
|
||||
iac: "Infrastructure as Code",
|
||||
};
|
||||
|
||||
interface EnhancedProviderSelectorProps {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
@@ -78,6 +79,12 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
|
||||
<span className="ml-2">GitHub</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="Infrastructure as Code" value="iac">
|
||||
<div className="flex items-center">
|
||||
<IacProviderBadge size={26} />
|
||||
<span className="ml-2">Infrastructure as Code</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
AzureCredentials,
|
||||
GCPDefaultCredentials,
|
||||
GCPServiceAccountKey,
|
||||
IacCredentials,
|
||||
KubernetesCredentials,
|
||||
M365Credentials,
|
||||
ProviderType,
|
||||
@@ -28,6 +29,7 @@ import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credent
|
||||
import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
|
||||
import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
|
||||
import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form";
|
||||
import { IacCredentialsForm } from "./via-credentials/iac-credentials-form";
|
||||
import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
|
||||
import { M365CredentialsForm } from "./via-credentials/m365-credentials-form";
|
||||
|
||||
@@ -133,6 +135,11 @@ export const BaseCredentialsForm = ({
|
||||
credentialsType={searchParamsObj.get("via") || undefined}
|
||||
/>
|
||||
)}
|
||||
{providerType === "iac" && (
|
||||
<IacCredentialsForm
|
||||
control={form.control as unknown as Control<IacCredentials>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
|
||||
|
||||
@@ -51,6 +51,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
|
||||
label: "Username",
|
||||
placeholder: "e.g. your-github-username",
|
||||
};
|
||||
case "iac":
|
||||
return {
|
||||
label: "Repository URL",
|
||||
placeholder: "e.g. https://github.com/user/repo",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Provider UID",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { IacCredentials } from "@/types";
|
||||
|
||||
export const IacCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<IacCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect via Repository
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide the repository URL to scan for Infrastructure as Code
|
||||
files.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="repository_url"
|
||||
label="Repository URL"
|
||||
labelPlacement="inside"
|
||||
placeholder="https://github.com/user/repo or https://github.com/user/repo.git"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.repository_url}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="access_token"
|
||||
label="Access Token (Optional)"
|
||||
labelPlacement="inside"
|
||||
placeholder="Token for private repositories (optional)"
|
||||
variant="bordered"
|
||||
type="password"
|
||||
isInvalid={!!control._formState.errors.access_token}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./azure-credentials-form";
|
||||
export * from "./github-credentials-form";
|
||||
export * from "./iac-credentials-form";
|
||||
export * from "./k8s-credentials-form";
|
||||
export * from "./m365-credentials-form";
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
} from "@/components/icons/providers-badge";
|
||||
@@ -24,6 +25,8 @@ export const getProviderLogo = (provider: ProviderType) => {
|
||||
return <M365ProviderBadge width={35} height={35} />;
|
||||
case "github":
|
||||
return <GitHubProviderBadge width={35} height={35} />;
|
||||
case "iac":
|
||||
return <IacProviderBadge width={35} height={35} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -43,6 +46,8 @@ export const getProviderName = (provider: ProviderType): string => {
|
||||
return "Microsoft 365";
|
||||
case "github":
|
||||
return "GitHub";
|
||||
case "iac":
|
||||
return "Infrastructure as Code";
|
||||
default:
|
||||
return "Unknown Provider";
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ export const getProviderHelpText = (provider: string) => {
|
||||
text: "Need help connecting your GitHub account?",
|
||||
link: "https://goto.prowler.com/provider-github",
|
||||
};
|
||||
case "iac":
|
||||
return {
|
||||
text: "Need help scanning your Infrastructure as Code repository?",
|
||||
link: "https://goto.prowler.com/provider-iac",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: "How to setup a provider?",
|
||||
|
||||
@@ -190,6 +190,20 @@ export const buildGitHubSecret = (formData: FormData) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const buildIacSecret = (formData: FormData) => {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.REPOSITORY_URL]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.REPOSITORY_URL,
|
||||
),
|
||||
[ProviderCredentialFields.ACCESS_TOKEN]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.ACCESS_TOKEN,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
// Main function to build secret configuration
|
||||
export const buildSecretConfig = (
|
||||
formData: FormData,
|
||||
@@ -224,6 +238,10 @@ export const buildSecretConfig = (
|
||||
secretType: "static",
|
||||
secret: buildGitHubSecret(formData),
|
||||
}),
|
||||
iac: () => ({
|
||||
secretType: "static",
|
||||
secret: buildIacSecret(formData),
|
||||
}),
|
||||
};
|
||||
|
||||
const builder = secretBuilders[providerType];
|
||||
|
||||
@@ -42,6 +42,10 @@ export const ProviderCredentialFields = {
|
||||
OAUTH_APP_TOKEN: "oauth_app_token",
|
||||
GITHUB_APP_ID: "github_app_id",
|
||||
GITHUB_APP_KEY: "github_app_key_content",
|
||||
|
||||
// IaC fields
|
||||
REPOSITORY_URL: "repository_url",
|
||||
ACCESS_TOKEN: "access_token",
|
||||
} as const;
|
||||
|
||||
// Type for credential field values
|
||||
@@ -70,6 +74,8 @@ export const ErrorPointers = {
|
||||
OAUTH_APP_TOKEN: "/data/attributes/secret/oauth_app_token",
|
||||
GITHUB_APP_ID: "/data/attributes/secret/github_app_id",
|
||||
GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content",
|
||||
REPOSITORY_URL: "/data/attributes/secret/repository_url",
|
||||
ACCESS_TOKEN: "/data/attributes/secret/access_token",
|
||||
} as const;
|
||||
|
||||
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];
|
||||
|
||||
@@ -239,12 +239,19 @@ export type KubernetesCredentials = {
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type IacCredentials = {
|
||||
[ProviderCredentialFields.REPOSITORY_URL]: string;
|
||||
[ProviderCredentialFields.ACCESS_TOKEN]?: string;
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type CredentialsFormSchema =
|
||||
| AWSCredentials
|
||||
| AzureCredentials
|
||||
| GCPDefaultCredentials
|
||||
| GCPServiceAccountKey
|
||||
| KubernetesCredentials
|
||||
| IacCredentials
|
||||
| M365Credentials;
|
||||
|
||||
export interface SearchParamsProps {
|
||||
|
||||
@@ -110,6 +110,11 @@ export const addProviderFormSchema = z
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
providerType: z.literal("iac"),
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -190,7 +195,16 @@ export const addCredentialsFormSchema = (
|
||||
.string()
|
||||
.optional(),
|
||||
}
|
||||
: {}),
|
||||
: providerType === "iac"
|
||||
? {
|
||||
[ProviderCredentialFields.REPOSITORY_URL]: z
|
||||
.string()
|
||||
.nonempty("Repository URL is required"),
|
||||
[ProviderCredentialFields.ACCESS_TOKEN]: z
|
||||
.string()
|
||||
.optional(),
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
.superRefine((data: Record<string, any>, ctx) => {
|
||||
if (providerType === "m365") {
|
||||
|
||||
@@ -5,6 +5,7 @@ export const PROVIDER_TYPES = [
|
||||
"kubernetes",
|
||||
"m365",
|
||||
"github",
|
||||
"iac",
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
Reference in New Issue
Block a user