feat(api): add OpenStack provider support (#10003)

This commit is contained in:
Daniel Barranquero
2026-02-12 14:40:19 +01:00
committed by GitHub
parent e6bea9f25a
commit b94c8a5e5e
14 changed files with 507 additions and 7 deletions
+4
View File
@@ -4,6 +4,10 @@ All notable changes to the **Prowler API** are documented in this file.
## [1.20.0] (Prowler UNRELEASED)
### 🚀 Added
- OpenStack provider support [(#10003)](https://github.com/prowler-cloud/prowler/pull/10003)
### 🔄 Changed
- Attack Paths: Queries definition now has short description and attribution [(#9983)](https://github.com/prowler-cloud/prowler/pull/9983)
@@ -0,0 +1,39 @@
# Generated by Django migration for OpenStack provider support
from django.db import migrations
import api.db_utils
class Migration(migrations.Migration):
dependencies = [
("api", "0075_cloudflare_provider"),
]
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"),
("mongodbatlas", "MongoDB Atlas"),
("iac", "IaC"),
("oraclecloud", "Oracle Cloud Infrastructure"),
("alibabacloud", "Alibaba Cloud"),
("cloudflare", "Cloudflare"),
("openstack", "OpenStack"),
],
default="aws",
),
),
migrations.RunSQL(
"ALTER TYPE provider ADD VALUE IF NOT EXISTS 'openstack';",
reverse_sql=migrations.RunSQL.noop,
),
]
+10
View File
@@ -288,6 +288,7 @@ class Provider(RowLevelSecurityProtectedModel):
ORACLECLOUD = "oraclecloud", _("Oracle Cloud Infrastructure")
ALIBABACLOUD = "alibabacloud", _("Alibaba Cloud")
CLOUDFLARE = "cloudflare", _("Cloudflare")
OPENSTACK = "openstack", _("OpenStack")
@staticmethod
def validate_aws_uid(value):
@@ -410,6 +411,15 @@ class Provider(RowLevelSecurityProtectedModel):
pointer="/data/attributes/uid",
)
@staticmethod
def validate_openstack_uid(value):
if not re.match(r"^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$", value):
raise ModelValidationError(
detail="OpenStack provider ID must be a valid project ID (UUID or project name).",
code="openstack-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)
+266
View File
@@ -367,6 +367,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -380,6 +381,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -398,6 +400,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -413,6 +416,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -1348,6 +1352,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -1361,6 +1366,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -1379,6 +1385,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -1394,6 +1401,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -1938,6 +1946,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -1951,6 +1960,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -1969,6 +1979,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -1984,6 +1995,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -2436,6 +2448,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -2449,6 +2462,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -2467,6 +2481,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -2482,6 +2497,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -2932,6 +2948,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -2945,6 +2962,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -2963,6 +2981,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -2978,6 +2997,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -3416,6 +3436,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -3429,6 +3450,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -3447,6 +3469,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -3462,6 +3485,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -5241,6 +5265,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -5254,6 +5279,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -5272,6 +5298,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -5287,6 +5314,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- name: filter[search]
@@ -5404,6 +5432,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -5417,6 +5446,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -5435,6 +5465,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -5450,6 +5481,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- name: filter[search]
@@ -5556,6 +5588,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -5569,6 +5602,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -5586,6 +5620,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -5601,6 +5636,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- name: filter[search]
@@ -5739,6 +5775,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -5752,6 +5789,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -5770,6 +5808,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -5785,6 +5824,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -5936,6 +5976,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -5949,6 +5990,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -5967,6 +6009,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -5982,6 +6025,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -6127,6 +6171,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -6140,6 +6185,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -6157,6 +6203,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -6172,6 +6219,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- name: filter[search]
@@ -6359,6 +6407,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -6372,6 +6421,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -6390,6 +6440,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -6405,6 +6456,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -6521,6 +6573,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -6534,6 +6587,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -6552,6 +6606,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -6567,6 +6622,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -6707,6 +6763,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -6720,6 +6777,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -6738,6 +6796,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -6753,6 +6812,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -7534,6 +7594,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -7547,6 +7608,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider__in]
schema:
@@ -7565,6 +7627,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -7580,6 +7643,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -7598,6 +7662,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -7611,6 +7676,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -7629,6 +7695,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -7644,6 +7711,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- name: filter[search]
@@ -8285,6 +8353,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -8298,6 +8367,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -8316,6 +8386,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -8331,6 +8402,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -8787,6 +8859,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -8800,6 +8873,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -8818,6 +8892,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -8833,6 +8908,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -9102,6 +9178,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -9115,6 +9192,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -9133,6 +9211,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -9148,6 +9227,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -9423,6 +9503,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -9436,6 +9517,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -9454,6 +9536,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -9469,6 +9552,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -10278,6 +10362,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
* `aws` - AWS
@@ -10291,6 +10376,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
- in: query
name: filter[provider_type__in]
schema:
@@ -10309,6 +10395,7 @@ paths:
- kubernetes
- m365
- mongodbatlas
- openstack
- oraclecloud
description: |-
Multiple values may be separated by commas.
@@ -10324,6 +10411,7 @@ paths:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
explode: false
style: form
- in: query
@@ -17348,6 +17436,50 @@ components:
required:
- api_key
- api_email
- type: object
title: OpenStack clouds.yaml Credentials
properties:
clouds_yaml_content:
type: string
description: The full content of a clouds.yaml configuration
file.
clouds_yaml_cloud:
type: string
description: The name of the cloud to use from the clouds.yaml
file.
required:
- clouds_yaml_content
- clouds_yaml_cloud
- type: object
title: OpenStack Explicit Credentials
properties:
auth_url:
type: string
description: OpenStack Keystone authentication URL (e.g.,
https://openstack.example.com:5000/v3).
username:
type: string
description: OpenStack username for authentication.
password:
type: string
description: OpenStack password for authentication.
region_name:
type: string
description: OpenStack region name (e.g., RegionOne).
identity_api_version:
type: string
description: Keystone API version (default: 3).
user_domain_name:
type: string
description: User domain name (default: Default).
project_domain_name:
type: string
description: Project domain name (default: Default).
required:
- auth_url
- username
- password
- region_name
writeOnly: true
required:
- secret
@@ -18347,6 +18479,7 @@ components:
- oraclecloud
- alibabacloud
- cloudflare
- openstack
type: string
description: |-
* `aws` - AWS
@@ -18360,6 +18493,7 @@ components:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
x-spec-enum-id: 2d8d323e9cc0044b
uid:
type: string
@@ -18477,6 +18611,7 @@ components:
- oraclecloud
- alibabacloud
- cloudflare
- openstack
type: string
x-spec-enum-id: 2d8d323e9cc0044b
description: |-
@@ -18493,6 +18628,7 @@ components:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
uid:
type: string
title: Unique identifier for the provider, set by the provider
@@ -18541,6 +18677,7 @@ components:
- oraclecloud
- alibabacloud
- cloudflare
- openstack
type: string
x-spec-enum-id: 2d8d323e9cc0044b
description: |-
@@ -18557,6 +18694,7 @@ components:
* `oraclecloud` - Oracle Cloud Infrastructure
* `alibabacloud` - Alibaba Cloud
* `cloudflare` - Cloudflare
* `openstack` - OpenStack
uid:
type: string
minLength: 3
@@ -19378,6 +19516,48 @@ components:
required:
- api_key
- api_email
- type: object
title: OpenStack clouds.yaml Credentials
properties:
clouds_yaml_content:
type: string
description: The full content of a clouds.yaml configuration file.
clouds_yaml_cloud:
type: string
description: The name of the cloud to use from the clouds.yaml
file.
required:
- clouds_yaml_content
- clouds_yaml_cloud
- type: object
title: OpenStack Explicit Credentials
properties:
auth_url:
type: string
description: OpenStack Keystone authentication URL (e.g., https://openstack.example.com:5000/v3).
username:
type: string
description: OpenStack username for authentication.
password:
type: string
description: OpenStack password for authentication.
region_name:
type: string
description: OpenStack region name (e.g., RegionOne).
identity_api_version:
type: string
description: Keystone API version (default: 3).
user_domain_name:
type: string
description: User domain name (default: Default).
project_domain_name:
type: string
description: Project domain name (default: Default).
required:
- auth_url
- username
- password
- region_name
writeOnly: true
required:
- secret_type
@@ -19764,6 +19944,50 @@ components:
required:
- api_key
- api_email
- type: object
title: OpenStack clouds.yaml Credentials
properties:
clouds_yaml_content:
type: string
description: The full content of a clouds.yaml configuration
file.
clouds_yaml_cloud:
type: string
description: The name of the cloud to use from the clouds.yaml
file.
required:
- clouds_yaml_content
- clouds_yaml_cloud
- type: object
title: OpenStack Explicit Credentials
properties:
auth_url:
type: string
description: OpenStack Keystone authentication URL (e.g.,
https://openstack.example.com:5000/v3).
username:
type: string
description: OpenStack username for authentication.
password:
type: string
description: OpenStack password for authentication.
region_name:
type: string
description: OpenStack region name (e.g., RegionOne).
identity_api_version:
type: string
description: Keystone API version (default: 3).
user_domain_name:
type: string
description: User domain name (default: Default).
project_domain_name:
type: string
description: Project domain name (default: Default).
required:
- auth_url
- username
- password
- region_name
writeOnly: true
required:
- secret_type
@@ -20162,6 +20386,48 @@ components:
required:
- api_key
- api_email
- type: object
title: OpenStack clouds.yaml Credentials
properties:
clouds_yaml_content:
type: string
description: The full content of a clouds.yaml configuration file.
clouds_yaml_cloud:
type: string
description: The name of the cloud to use from the clouds.yaml
file.
required:
- clouds_yaml_content
- clouds_yaml_cloud
- type: object
title: OpenStack Explicit Credentials
properties:
auth_url:
type: string
description: OpenStack Keystone authentication URL (e.g., https://openstack.example.com:5000/v3).
username:
type: string
description: OpenStack username for authentication.
password:
type: string
description: OpenStack password for authentication.
region_name:
type: string
description: OpenStack region name (e.g., RegionOne).
identity_api_version:
type: string
description: Keystone API version (default: 3).
user_domain_name:
type: string
description: User domain name (default: Default).
project_domain_name:
type: string
description: Project domain name (default: Default).
required:
- auth_url
- username
- password
- region_name
writeOnly: true
required:
- secret
+6
View File
@@ -27,6 +27,7 @@ from prowler.providers.iac.iac_provider import IacProvider
from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider
from prowler.providers.m365.m365_provider import M365Provider
from prowler.providers.mongodbatlas.mongodbatlas_provider import MongodbatlasProvider
from prowler.providers.openstack.openstack_provider import OpenstackProvider
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
@@ -120,6 +121,7 @@ class TestReturnProwlerProvider:
(Provider.ProviderChoices.IAC.value, IacProvider),
(Provider.ProviderChoices.ALIBABACLOUD.value, AlibabacloudProvider),
(Provider.ProviderChoices.CLOUDFLARE.value, CloudflareProvider),
(Provider.ProviderChoices.OPENSTACK.value, OpenstackProvider),
],
)
def test_return_prowler_provider(self, provider_type, expected_provider):
@@ -227,6 +229,10 @@ class TestGetProwlerProviderKwargs:
Provider.ProviderChoices.CLOUDFLARE.value,
{"filter_accounts": ["provider_uid"]},
),
(
Provider.ProviderChoices.OPENSTACK.value,
{},
),
],
)
def test_get_prowler_provider_kwargs(self, provider_type, expected_extra_kwargs):
+38 -4
View File
@@ -1179,6 +1179,11 @@ class TestProviderViewSet:
"uid": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"alias": "Cloudflare Account",
},
{
"provider": "openstack",
"uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"alias": "OpenStack Project",
},
]
),
)
@@ -1598,6 +1603,26 @@ class TestProviderViewSet:
"cloudflare-uid",
"uid",
),
# OpenStack UID validation - starts with special character
(
{
"provider": "openstack",
"uid": "-invalid-project",
"alias": "test",
},
"openstack-uid",
"uid",
),
# OpenStack UID validation - too short (below min_length)
(
{
"provider": "openstack",
"uid": "ab",
"alias": "test",
},
"min_length",
"uid",
),
]
),
)
@@ -1771,21 +1796,21 @@ class TestProviderViewSet:
(
"uid.icontains",
"1",
9,
10,
),
("alias", "aws_testing_1", 1),
("alias.icontains", "aws", 2),
("inserted_at", TODAY, 10),
("inserted_at", TODAY, 11),
(
"inserted_at.gte",
"2024-01-01",
10,
11,
),
("inserted_at.lte", "2024-01-01", 0),
(
"updated_at.gte",
"2024-01-01",
10,
11,
),
("updated_at.lte", "2024-01-01", 0),
]
@@ -2392,6 +2417,15 @@ class TestProviderSecretViewSet:
"api_email": "user@example.com",
},
),
# OpenStack with clouds.yaml content
(
Provider.ProviderChoices.OPENSTACK.value,
ProviderSecret.TypeChoices.STATIC,
{
"clouds_yaml_content": "clouds:\n mycloud:\n auth:\n auth_url: https://openstack.example.com:5000/v3\n",
"clouds_yaml_cloud": "mycloud",
},
),
],
)
def test_provider_secrets_create_valid(
+23 -2
View File
@@ -33,6 +33,7 @@ if TYPE_CHECKING:
from prowler.providers.mongodbatlas.mongodbatlas_provider import (
MongodbatlasProvider,
)
from prowler.providers.openstack.openstack_provider import OpenstackProvider
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
@@ -78,12 +79,14 @@ def return_prowler_provider(
AlibabacloudProvider
| AwsProvider
| AzureProvider
| CloudflareProvider
| GcpProvider
| GithubProvider
| IacProvider
| KubernetesProvider
| M365Provider
| MongodbatlasProvider
| OpenstackProvider
| OraclecloudProvider
):
"""Return the Prowler provider class based on the given provider type.
@@ -92,7 +95,7 @@ def return_prowler_provider(
provider (Provider): The provider object containing the provider type and associated secrets.
Returns:
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: The corresponding provider class.
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: The corresponding provider class.
Raises:
ValueError: If the provider type specified in `provider.provider` is not supported.
@@ -152,6 +155,10 @@ def return_prowler_provider(
)
prowler_provider = CloudflareProvider
case Provider.ProviderChoices.OPENSTACK.value:
from prowler.providers.openstack.openstack_provider import OpenstackProvider
prowler_provider = OpenstackProvider
case _:
raise ValueError(f"Provider type {provider.provider} not supported")
return prowler_provider
@@ -208,6 +215,12 @@ def get_prowler_provider_kwargs(
**prowler_provider_kwargs,
"filter_accounts": [provider.uid],
}
elif provider.provider == Provider.ProviderChoices.OPENSTACK.value:
# No extra kwargs needed: clouds_yaml_content and clouds_yaml_cloud from the
# secret are sufficient. Validating project_id (provider.uid) against the
# clouds.yaml is not feasible because not all auth methods include it and the
# Keystone API is unavailable on public clouds.
pass
if mutelist_processor:
mutelist_content = mutelist_processor.configuration.get("Mutelist", {})
@@ -232,6 +245,7 @@ def initialize_prowler_provider(
| KubernetesProvider
| M365Provider
| MongodbatlasProvider
| OpenstackProvider
| OraclecloudProvider
):
"""Initialize a Prowler provider instance based on the given provider type.
@@ -241,7 +255,7 @@ def initialize_prowler_provider(
mutelist_processor (Processor): The mutelist processor object containing the mutelist configuration.
Returns:
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OraclecloudProvider: An instance of the corresponding provider class
AlibabacloudProvider | AwsProvider | AzureProvider | CloudflareProvider | GcpProvider | GithubProvider | IacProvider | KubernetesProvider | M365Provider | MongodbatlasProvider | OpenstackProvider | OraclecloudProvider: An instance of the corresponding provider class
initialized with the provider's secrets.
"""
prowler_provider = return_prowler_provider(provider)
@@ -276,6 +290,13 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
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)
elif provider.provider == Provider.ProviderChoices.OPENSTACK.value:
openstack_kwargs = {
"clouds_yaml_content": prowler_provider_kwargs["clouds_yaml_content"],
"clouds_yaml_cloud": prowler_provider_kwargs["clouds_yaml_cloud"],
"raise_on_exception": False,
}
return prowler_provider.test_connection(**openstack_kwargs)
else:
return prowler_provider.test_connection(
**prowler_provider_kwargs,
@@ -373,6 +373,21 @@ from rest_framework_json_api import serializers
},
"required": ["api_key", "api_email"],
},
{
"type": "object",
"title": "OpenStack clouds.yaml Credentials",
"properties": {
"clouds_yaml_content": {
"type": "string",
"description": "The full content of a clouds.yaml configuration file.",
},
"clouds_yaml_cloud": {
"type": "string",
"description": "The name of the cloud to use from the clouds.yaml file.",
},
},
"required": ["clouds_yaml_content", "clouds_yaml_cloud"],
},
]
}
)
+10
View File
@@ -1525,6 +1525,8 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
"or both 'api_key' and 'api_email'."
}
)
elif provider_type == Provider.ProviderChoices.OPENSTACK.value:
serializer = OpenStackCloudsYamlProviderSecret(data=secret)
else:
raise serializers.ValidationError(
{"provider": f"Provider type not supported {provider_type}"}
@@ -1691,6 +1693,14 @@ class CloudflareApiKeyProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
class OpenStackCloudsYamlProviderSecret(serializers.Serializer):
clouds_yaml_content = serializers.CharField()
clouds_yaml_cloud = serializers.CharField()
class Meta:
resource_name = "provider-secrets"
class AlibabaCloudProviderSecret(serializers.Serializer):
access_key_id = serializers.CharField()
access_key_secret = serializers.CharField()
+4
View File
@@ -18,6 +18,10 @@ DATABASES = {
DATABASE_ROUTERS = []
TESTING = True
# Override page size for testing to a value only slightly above the current fixture count.
# We explicitly set PAGE_SIZE to 15 (round number just above fixture) to avoid masking pagination bugs, while not setting it excessively high.
# If you add more providers to the fixture, please review that the total value is below the current one and update this value if needed.
REST_FRAMEWORK["PAGE_SIZE"] = 15 # noqa: F405
SECRETS_ENCRYPTION_KEY = "ZMiYVo7m4Fbe2eXXPyrwxdJss2WSalXSv3xHBcJkPl0="
# DRF Simple API Key settings
+7
View File
@@ -537,6 +537,12 @@ def providers_fixture(tenants_fixture):
alias="cloudflare_testing",
tenant_id=tenant.id,
)
provider11 = Provider.objects.create(
provider="openstack",
uid="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
alias="openstack_testing",
tenant_id=tenant.id,
)
return (
provider1,
@@ -549,6 +555,7 @@ def providers_fixture(tenants_fixture):
provider8,
provider9,
provider10,
provider11,
)
+1
View File
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- OpenStack provider `clouds_yaml_content` parameter for API integration [(#10003)](https://github.com/prowler-cloud/prowler/pull/10003)
- `defender_safe_attachments_policy_enabled` check for M365 provider [(#9833)](https://github.com/prowler-cloud/prowler/pull/9833)
- `defender_safelinks_policy_enabled` check for M365 provider [(#9832)](https://github.com/prowler-cloud/prowler/pull/9832)
- AI Skills: Added a skill for creating new Attack Paths queries in openCypher, compatible with Neo4j and Neptune [(#9975)](https://github.com/prowler-cloud/prowler/pull/9975)
+3
View File
@@ -297,6 +297,9 @@ class Provider(ABC):
elif "openstack" in provider_class_name.lower():
provider_class(
clouds_yaml_file=getattr(arguments, "clouds_yaml_file", None),
clouds_yaml_content=getattr(
arguments, "clouds_yaml_content", None
),
clouds_yaml_cloud=getattr(arguments, "clouds_yaml_cloud", None),
auth_url=getattr(arguments, "os_auth_url", None),
identity_api_version=getattr(
@@ -6,6 +6,7 @@ from colorama import Fore, Style
from openstack import config, connect
from openstack import exceptions as openstack_exceptions
from openstack.connection import Connection as OpenStackConnection
from yaml import YAMLError, safe_load
from prowler.config.config import (
default_config_file_path,
@@ -49,6 +50,7 @@ class OpenstackProvider(Provider):
def __init__(
self,
clouds_yaml_file: Optional[str] = None,
clouds_yaml_content: Optional[str] = None,
clouds_yaml_cloud: Optional[str] = None,
auth_url: Optional[str] = None,
identity_api_version: Optional[str] = None,
@@ -68,6 +70,7 @@ class OpenstackProvider(Provider):
self._session = self.setup_session(
clouds_yaml_file=clouds_yaml_file,
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud=clouds_yaml_cloud,
auth_url=auth_url,
identity_api_version=identity_api_version,
@@ -132,6 +135,7 @@ class OpenstackProvider(Provider):
@staticmethod
def setup_session(
clouds_yaml_file: Optional[str] = None,
clouds_yaml_content: Optional[str] = None,
clouds_yaml_cloud: Optional[str] = None,
auth_url: Optional[str] = None,
identity_api_version: Optional[str] = None,
@@ -145,10 +149,16 @@ class OpenstackProvider(Provider):
"""Collect authentication information from clouds.yaml, explicit parameters, or environment variables.
Authentication priority:
1. clouds.yaml file (if clouds_yaml_file or clouds_yaml_cloud provided)
1. clouds.yaml content/file (if clouds_yaml_content, clouds_yaml_file, or clouds_yaml_cloud provided)
2. Explicit parameters + environment variable fallback
"""
# Priority 1: clouds.yaml authentication
if clouds_yaml_content:
logger.info("Using clouds.yaml content string for authentication")
return OpenstackProvider._setup_session_from_clouds_yaml_content(
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud=clouds_yaml_cloud,
)
if clouds_yaml_file or clouds_yaml_cloud:
logger.info("Using clouds.yaml configuration for authentication")
return OpenstackProvider._setup_session_from_clouds_yaml(
@@ -203,6 +213,73 @@ class OpenstackProvider(Provider):
project_domain_name=resolved_project_domain,
)
@staticmethod
def _setup_session_from_clouds_yaml_content(
clouds_yaml_content: str,
clouds_yaml_cloud: Optional[str] = None,
) -> OpenStackSession:
"""Setup session from clouds.yaml content provided as a string.
Parses the YAML content directly instead of writing to a temporary file,
following the same pattern as KubernetesProvider.setup_session().
Args:
clouds_yaml_content: The full YAML content of a clouds.yaml file.
clouds_yaml_cloud: Cloud name to use from the clouds.yaml content.
Returns:
OpenStackSession configured from the provided clouds.yaml content.
Raises:
OpenStackInvalidConfigError: If the YAML is malformed or missing required fields.
OpenStackCloudNotFoundError: If the specified cloud is not found in the content.
"""
if not clouds_yaml_cloud:
raise OpenStackInvalidConfigError(
message="Cloud name (--clouds-yaml-cloud) is required when using clouds.yaml content",
)
try:
parsed = safe_load(clouds_yaml_content)
except YAMLError as error:
raise OpenStackInvalidConfigError(
original_exception=error,
message=f"Failed to parse clouds.yaml content: {error}",
)
if not isinstance(parsed, dict) or "clouds" not in parsed:
raise OpenStackInvalidConfigError(
message="Invalid clouds.yaml content: missing 'clouds' key",
)
cloud_config = parsed["clouds"].get(clouds_yaml_cloud)
if not cloud_config:
raise OpenStackCloudNotFoundError(
message=f"Cloud '{clouds_yaml_cloud}' not found in clouds.yaml content",
)
auth_dict = cloud_config.get("auth", {})
required_fields = ["auth_url", "username", "password"]
missing_fields = [
field for field in required_fields if not auth_dict.get(field)
]
if missing_fields:
raise OpenStackInvalidConfigError(
message=f"Missing required fields in clouds.yaml for cloud '{clouds_yaml_cloud}': {', '.join(missing_fields)}",
)
return OpenStackSession(
auth_url=auth_dict.get("auth_url"),
identity_api_version=str(cloud_config.get("identity_api_version", "3")),
username=auth_dict.get("username"),
password=auth_dict.get("password"),
project_id=auth_dict.get("project_id") or auth_dict.get("project_name"),
region_name=cloud_config.get("region_name"),
user_domain_name=auth_dict.get("user_domain_name", "Default"),
project_domain_name=auth_dict.get("project_domain_name", "Default"),
)
@staticmethod
def _setup_session_from_clouds_yaml(
clouds_yaml_file: Optional[str] = None,
@@ -394,6 +471,7 @@ class OpenstackProvider(Provider):
@staticmethod
def test_connection(
clouds_yaml_file: Optional[str] = None,
clouds_yaml_content: Optional[str] = None,
clouds_yaml_cloud: Optional[str] = None,
auth_url: Optional[str] = None,
identity_api_version: Optional[str] = None,
@@ -412,6 +490,7 @@ class OpenstackProvider(Provider):
Args:
clouds_yaml_file: Path to clouds.yaml configuration file
clouds_yaml_content: The full content of a clouds.yaml file as a string
clouds_yaml_cloud: Cloud name from clouds.yaml to use
auth_url: OpenStack Keystone authentication URL
identity_api_version: Keystone API version (default: "3")
@@ -456,6 +535,7 @@ class OpenstackProvider(Provider):
# Setup session with provided credentials
session = OpenstackProvider.setup_session(
clouds_yaml_file=clouds_yaml_file,
clouds_yaml_content=clouds_yaml_content,
clouds_yaml_cloud=clouds_yaml_cloud,
auth_url=auth_url,
identity_api_version=identity_api_version,