Introduction
In Prowler, a service represents a specific solution or resource offered by one of the supported Prowler Providers, for example, EC2 in AWS, or Microsoft Exchange in M365. Services are the building blocks that allow Prowler interact directly with the various resources exposed by each provider. Each service is implemented as a class that encapsulates all the logic, data models, and API interactions required to gather and store information about that service’s resources. All of this data is used by the Prowler checks to generate the security findings.Adding a New Service
To create a new service, a new folder must be created inside the specific provider following this pattern:prowler/providers/<provider>/services/<new_service_name>/.
Within this folder the following files are also to be created:
__init__.py(empty) – Ensures Python recognizes this folder as a package.<new_service_name>_service.py– Contains all the logic and API calls of the service.<new_service_name>_client_.py– Contains the initialization of the freshly created service’s class so that the checks can use it.
uv run python prowler-cli.py <provider> --list-services | grep <new_service_name>.
Service Structure and Initialisation
The Prowler’s service structure is as outlined below. To initialise it, just import the service client in a check.Service Base Class
All Prowler provider service should inherit from a common base class to avoid code duplication. This base class handles initialization and storage of functions and objects needed across services. The exact implementation depends on the provider’s API requirements, but the following are the most common responsibilities:- Initialize/store clients to interact with the provider’s API.
- Store the audit and fixer configuration.
- Implement threading logic where applicable.
- AWS Service Base Class
- GCP Service Base Class
- Azure Service Base Class
- Kubernetes Service Base Class
- M365 Service Base Class
- GitHub Service Base Class
Service Class
Due to the complexity and differences across provider APIs, the following example demonstrates best practices for structuring a service in Prowler. File<new_service_name>_service.py:
Example Service Class
To prevent false findings, when Prowler fails to retrieve items due to Access Denied or similar errors, the affected item’s value is set to
None.Resource Models
Resource models define structured classes used within services to store and process data extracted from API calls. They are defined in the same file as the service class, but outside of the class, usually at the bottom of the file. Prowler leverages Pydantic’s BaseModel to enforce data validation.Service Model
Service Attributes
Optimized Data Storage with Python Dictionaries Each group of resources within a service should be structured as a Python dictionary to enable efficient lookups. The dictionary lookup operation has O(1) complexity, and lookups are constantly executed. Assigning Unique Identifiers Each dictionary key must be a unique ID to identify the resource in a univocal way. Example:Service Client
Each Prowler service requires a service client to use the service in the checks. The following is the<new_service_name>_client.py file, which contains the initialization of the freshly created service’s class so that service checks can use it. This file is almost the same for all the services among the providers:
Provider Permissions in Prowler
Before implementing a new service, verify that Prowler’s existing permissions for each provider are sufficient. If additional permissions are required, refer to the relevant documentation and update accordingly. Provider-Specific Permissions Documentation:Service Architecture and Cross-Service Communication
Core Principle: Service Isolation with Client Communication
Each service must contain ONLY the information unique to that specific service. When a check requires information from multiple services, it must use the client objects of other services rather than directly accessing their data structures. This architecture ensures:- Loose coupling between services
- Clear separation of concerns
- Maintainable and testable code
- Consistent data access patterns
Cross-Service Communication Pattern
Instead of services directly accessing each other’s internal data, checks should import and use client objects: ❌ INCORRECT - Direct data access:Real-World Example: CloudTrail + S3 Integration
This example demonstrates how CloudTrail checks validate S3 bucket configurations:- CloudTrail service only contains CloudTrail-specific data (trails, configurations)
- S3 service only contains S3-specific data (buckets, policies, ACLs)
- Check logic orchestrates between services using their public client interfaces
- Cross-account detection is handled gracefully when resources span accounts
Service Consolidation Guidelines
When to combine services in the same file: Implement multiple services as separate classes in the same file when two services are practically the same or one is a direct extension of another. Example: S3 and S3Control S3Control is an extension of S3 that provides account-level controls and access points. Both are implemented ins3_service.py:
- Operate on different resource types (EC2 vs RDS)
- Have different authentication mechanisms (different API endpoints)
- Serve different operational domains (IAM vs CloudTrail)
- Have different regional behaviors (global vs regional services)
Cross-Service Dependencies Guidelines
1. Always use client imports:Regional Service Implementation
When implementing services for regional providers (like AWS, Azure, GCP), special considerations are needed to handle resource discovery across multiple geographic locations. This section provides a complete guide using AWS as the reference example.Regional vs Non-Regional Services
Regional Services: Require iteration across multiple geographic locations where resources may exist (e.g., EC2 instances, VPC, RDS databases). Non-Regional/Global Services: Operate at a global or tenant level without regional concepts (e.g., IAM users, Route53 hosted zones).AWS Regional Implementation Example
AWS is the perfect example of a regional provider. Here’s how Prowler handles AWS’s regional architecture:Regional Check Execution
Key AWS Regional Features
Region-Specific ARNs:- Each region processed independently in separate threads
- Failed regions don’t affect other regions
- User can filter specific regions:
-f us-east-1
- Regional: EC2, RDS, VPC (require region iteration)
- Global: IAM, Route53, CloudFront (single
us-east-1call)
Regional Service Best Practices
- Use Threading for Regional Discovery: Leverage the
__threading_call__method to parallelize resource discovery across regions - Store Region Information: Always include region metadata in resource objects for proper attribution
- Handle Regional Failures Gracefully: Ensure that failures in one region don’t affect others
- Optimize for Performance: Use paginated calls and efficient data structures for large-scale resource discovery
- Support Region Filtering: Allow users to limit scans to specific regions for focused audits
Best Practices
- When available in the provider, use threading or parallelization utilities for all methods that can be parallelized by to maximize performance and reduce scan time.
- Define a Pydantic
BaseModelfor every resource you manage, and use these models for all resource data handling. - Log every major step (start, success, error) in resource discovery and attribute collection for traceability and debugging; include as much context as possible.
- Catch and log all exceptions, providing detailed context (region, subscription, resource, error type, line number) to aid troubleshooting.
- Use consistent naming for resource containers, unique identifiers, and model attributes to improve code readability and maintainability.
- Add docstrings to every method and comments to explain any service-specific logic, especially where provider APIs behave differently or have quirks.
- Collect and store resource tags and additional attributes to support richer checks and reporting.
- Leverage shared utility helpers for session setup, identifier parsing, and other cross-cutting concerns to avoid code duplication. This kind of code is typically stored in a
libfolder in the service folder. - Keep code modular, maintainable, and well-documented for ease of extension and troubleshooting.
- Each service should contain only information unique to that specific service - use client objects for cross-service communication.
- Handle cross-account and missing resources gracefully when checks span multiple services.

