mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
feat: add AI skills pack for Claude Code and OpenCode (#9728)
Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com> Co-authored-by: Adrián Jesús Peña Rodríguez <adrianjpr@gmail.com> Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
co-authored by
Rubén De la Torre Vico
Adrián Jesús Peña Rodríguez
Pepe Fagoaga
parent
42ae5b6e3e
commit
7f2fa275c6
@@ -0,0 +1,143 @@
|
||||
---
|
||||
name: prowler-provider
|
||||
description: >
|
||||
Creates new Prowler cloud providers or adds services to existing providers.
|
||||
Trigger: When adding a new cloud provider or service to Prowler SDK.
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: prowler-cloud
|
||||
version: "1.0"
|
||||
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when:
|
||||
- Adding a new cloud provider to Prowler
|
||||
- Adding a new service to an existing provider
|
||||
- Understanding the provider architecture pattern
|
||||
|
||||
## Provider Architecture Pattern
|
||||
|
||||
Every provider MUST follow this structure:
|
||||
|
||||
```
|
||||
prowler/providers/{provider}/
|
||||
├── __init__.py
|
||||
├── {provider}_provider.py # Main provider class
|
||||
├── models.py # Provider-specific models
|
||||
├── config.py # Provider configuration
|
||||
├── exceptions/ # Provider-specific exceptions
|
||||
├── lib/
|
||||
│ ├── service/ # Base service class
|
||||
│ ├── arguments/ # CLI arguments parser
|
||||
│ └── mutelist/ # Mutelist functionality
|
||||
└── services/
|
||||
└── {service}/
|
||||
├── {service}_service.py # Resource fetcher
|
||||
├── {service}_client.py # Python singleton instance
|
||||
└── {check_name}/ # Individual checks
|
||||
├── {check_name}.py
|
||||
└── {check_name}.metadata.json
|
||||
```
|
||||
|
||||
## Provider Class Template
|
||||
|
||||
```python
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
class {Provider}Provider(Provider):
|
||||
"""Provider class for {Provider} cloud platform."""
|
||||
|
||||
def __init__(self, arguments):
|
||||
super().__init__(arguments)
|
||||
self.session = self._setup_session(arguments)
|
||||
self.regions = self._get_regions()
|
||||
|
||||
def _setup_session(self, arguments):
|
||||
"""Provider-specific authentication."""
|
||||
# Implement credential handling
|
||||
pass
|
||||
|
||||
def _get_regions(self):
|
||||
"""Get available regions for provider."""
|
||||
# Return list of regions
|
||||
pass
|
||||
```
|
||||
|
||||
## Service Class Template
|
||||
|
||||
```python
|
||||
from prowler.providers.{provider}.lib.service.service import {Provider}Service
|
||||
|
||||
class {Service}({Provider}Service):
|
||||
"""Service class for {service} resources."""
|
||||
|
||||
def __init__(self, provider):
|
||||
super().__init__(provider)
|
||||
self.{resources} = []
|
||||
self._fetch_{resources}()
|
||||
|
||||
def _fetch_{resources}(self):
|
||||
"""Fetch {resource} data from API."""
|
||||
try:
|
||||
response = self.client.list_{resources}()
|
||||
for item in response:
|
||||
self.{resources}.append(
|
||||
{Resource}(
|
||||
id=item["id"],
|
||||
name=item["name"],
|
||||
region=item.get("region"),
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching {resources}: {e}")
|
||||
```
|
||||
|
||||
## Service Client Template
|
||||
|
||||
```python
|
||||
from prowler.providers.{provider}.services.{service}.{service}_service import {Service}
|
||||
|
||||
{service}_client = {Service}
|
||||
```
|
||||
|
||||
## Supported Providers
|
||||
|
||||
Current providers:
|
||||
- AWS (Amazon Web Services)
|
||||
- Azure (Microsoft Azure)
|
||||
- GCP (Google Cloud Platform)
|
||||
- Kubernetes
|
||||
- GitHub
|
||||
- M365 (Microsoft 365)
|
||||
- OracleCloud (Oracle Cloud Infrastructure)
|
||||
- AlibabaCloud
|
||||
- MongoDB Atlas
|
||||
- NHN (NHN Cloud)
|
||||
- LLM (Language Model providers)
|
||||
- IaC (Infrastructure as Code)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Run provider
|
||||
poetry run python prowler-cli.py {provider}
|
||||
|
||||
# List services for provider
|
||||
poetry run python prowler-cli.py {provider} --list-services
|
||||
|
||||
# List checks for provider
|
||||
poetry run python prowler-cli.py {provider} --list-checks
|
||||
|
||||
# Run specific service
|
||||
poetry run python prowler-cli.py {provider} --services {service}
|
||||
|
||||
# Debug mode
|
||||
poetry run python prowler-cli.py {provider} --log-level DEBUG
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Templates**: See [assets/](assets/) for Provider, Service, and Client singleton templates
|
||||
- **Documentation**: See [references/provider-docs.md](references/provider-docs.md) for official Prowler Developer Guide links
|
||||
@@ -0,0 +1,56 @@
|
||||
# Example: Singleton Client Pattern
|
||||
# Source: prowler/providers/github/services/repository/repository_client.py
|
||||
|
||||
"""
|
||||
Singleton Client Pattern
|
||||
|
||||
This pattern is CRITICAL for how Prowler checks access service data.
|
||||
|
||||
How it works:
|
||||
1. When this module is imported, the service is instantiated ONCE
|
||||
2. The service fetches all data during __init__ (eager loading)
|
||||
3. All checks import this singleton and access pre-fetched data
|
||||
4. No additional API calls needed during check execution
|
||||
|
||||
File: prowler/providers/github/services/repository/repository_client.py
|
||||
"""
|
||||
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.github.services.repository.repository_service import Repository
|
||||
|
||||
# SINGLETON: Instantiated once when module is first imported
|
||||
# Provider.get_global_provider() returns the provider set in __init__
|
||||
repository_client = Repository(Provider.get_global_provider())
|
||||
|
||||
|
||||
"""
|
||||
Usage in checks:
|
||||
|
||||
from prowler.providers.github.services.repository.repository_client import (
|
||||
repository_client,
|
||||
)
|
||||
|
||||
class repository_secret_scanning_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for repo in repository_client.repositories.values():
|
||||
# Access pre-fetched repository data
|
||||
report = CheckReportGithub(metadata=self.metadata(), resource=repo)
|
||||
if repo.secret_scanning_enabled:
|
||||
report.status = "PASS"
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
findings.append(report)
|
||||
return findings
|
||||
"""
|
||||
|
||||
|
||||
# Another example for organization service
|
||||
# File: prowler/providers/github/services/organization/organization_client.py
|
||||
|
||||
# from prowler.providers.common.provider import Provider
|
||||
# from prowler.providers.github.services.organization.organization_service import (
|
||||
# Organization,
|
||||
# )
|
||||
#
|
||||
# organization_client = Organization(Provider.get_global_provider())
|
||||
@@ -0,0 +1,143 @@
|
||||
# Example: Provider Class Template (GitHub Provider)
|
||||
# Source: prowler/providers/github/github_provider.py
|
||||
|
||||
|
||||
from prowler.config.config import (
|
||||
default_config_file_path,
|
||||
get_default_mute_file_path,
|
||||
load_and_validate_config_file,
|
||||
)
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.providers.common.models import Audit_Metadata, Connection
|
||||
from prowler.providers.common.provider import Provider
|
||||
|
||||
|
||||
class GithubProvider(Provider):
|
||||
"""
|
||||
GitHub Provider - Template for creating new providers.
|
||||
|
||||
Required attributes (from abstract Provider):
|
||||
- _type: str - Provider identifier
|
||||
- _session: Session model - Authentication credentials
|
||||
- _identity: Identity model - Authenticated user info
|
||||
- _audit_config: dict - Check configuration
|
||||
- _mutelist: Mutelist - Finding filtering
|
||||
"""
|
||||
|
||||
_type: str = "github"
|
||||
_auth_method: str = None
|
||||
_session: "GithubSession"
|
||||
_identity: "GithubIdentityInfo"
|
||||
_audit_config: dict
|
||||
_mutelist: Mutelist
|
||||
audit_metadata: Audit_Metadata
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# Authentication credentials
|
||||
personal_access_token: str = "",
|
||||
# Provider configuration
|
||||
config_path: str = None,
|
||||
config_content: dict = None,
|
||||
fixer_config: dict = {},
|
||||
mutelist_path: str = None,
|
||||
mutelist_content: dict = None,
|
||||
# Provider scoping
|
||||
repositories: list = None,
|
||||
organizations: list = None,
|
||||
):
|
||||
logger.info("Instantiating GitHub Provider...")
|
||||
|
||||
# Store scoping configuration
|
||||
self._repositories = repositories or []
|
||||
self._organizations = organizations or []
|
||||
|
||||
# Step 1: Setup session (authentication)
|
||||
self._session = self.setup_session(personal_access_token)
|
||||
self._auth_method = "Personal Access Token"
|
||||
|
||||
# Step 2: Setup identity (who is authenticated)
|
||||
self._identity = self.setup_identity(self._session)
|
||||
|
||||
# Step 3: Load audit config
|
||||
if config_content:
|
||||
self._audit_config = config_content
|
||||
else:
|
||||
if not config_path:
|
||||
config_path = default_config_file_path
|
||||
self._audit_config = load_and_validate_config_file(self._type, config_path)
|
||||
|
||||
# Step 4: Load fixer config
|
||||
self._fixer_config = fixer_config
|
||||
|
||||
# Step 5: Load mutelist
|
||||
if mutelist_content:
|
||||
self._mutelist = GithubMutelist(mutelist_content=mutelist_content)
|
||||
else:
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
self._mutelist = GithubMutelist(mutelist_path=mutelist_path)
|
||||
|
||||
# CRITICAL: Register as global provider
|
||||
Provider.set_global_provider(self)
|
||||
|
||||
# Required property implementations
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def session(self) -> "GithubSession":
|
||||
return self._session
|
||||
|
||||
@property
|
||||
def identity(self) -> "GithubIdentityInfo":
|
||||
return self._identity
|
||||
|
||||
@property
|
||||
def audit_config(self) -> dict:
|
||||
return self._audit_config
|
||||
|
||||
@property
|
||||
def mutelist(self) -> Mutelist:
|
||||
return self._mutelist
|
||||
|
||||
@staticmethod
|
||||
def setup_session(personal_access_token: str) -> "GithubSession":
|
||||
"""Create authenticated session from credentials."""
|
||||
if not personal_access_token:
|
||||
raise ValueError("Personal access token required")
|
||||
return GithubSession(token=personal_access_token)
|
||||
|
||||
@staticmethod
|
||||
def setup_identity(session: "GithubSession") -> "GithubIdentityInfo":
|
||||
"""Get identity info for authenticated user."""
|
||||
# Make API call to get user info
|
||||
# g = Github(auth=Auth.Token(session.token))
|
||||
# user = g.get_user()
|
||||
return GithubIdentityInfo(
|
||||
account_id="user-id",
|
||||
account_name="username",
|
||||
account_url="https://github.com/username",
|
||||
)
|
||||
|
||||
def print_credentials(self):
|
||||
"""Display credentials in CLI output."""
|
||||
print(f"GitHub Account: {self.identity.account_name}")
|
||||
print(f"Auth Method: {self._auth_method}")
|
||||
|
||||
@staticmethod
|
||||
def test_connection(
|
||||
personal_access_token: str = None,
|
||||
raise_on_exception: bool = True,
|
||||
) -> Connection:
|
||||
"""Test if credentials can connect to the provider."""
|
||||
try:
|
||||
session = GithubProvider.setup_session(personal_access_token)
|
||||
GithubProvider.setup_identity(session)
|
||||
return Connection(is_connected=True)
|
||||
except Exception as e:
|
||||
if raise_on_exception:
|
||||
raise
|
||||
return Connection(is_connected=False, error=str(e))
|
||||
@@ -0,0 +1,119 @@
|
||||
# Example: Service Base Class and Implementation
|
||||
# Source: prowler/providers/github/lib/service/service.py
|
||||
# Source: prowler/providers/github/services/repository/repository_service.py
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
|
||||
# ============================================================
|
||||
# Base Service Class
|
||||
# ============================================================
|
||||
|
||||
|
||||
class GithubService:
|
||||
"""
|
||||
Base service class for all GitHub services.
|
||||
|
||||
Key patterns:
|
||||
1. Receives provider in __init__
|
||||
2. Creates API clients in __set_clients__
|
||||
3. Stores audit_config and fixer_config for check access
|
||||
"""
|
||||
|
||||
def __init__(self, service: str, provider: "GithubProvider"):
|
||||
self.provider = provider
|
||||
self.clients = self.__set_clients__(provider.session)
|
||||
self.audit_config = provider.audit_config
|
||||
self.fixer_config = provider.fixer_config
|
||||
|
||||
def __set_clients__(self, session: "GithubSession") -> list:
|
||||
"""Create API clients based on authentication type."""
|
||||
clients = []
|
||||
try:
|
||||
# Create client(s) based on session credentials
|
||||
# For token auth: single client
|
||||
# For GitHub App: multiple clients (one per installation)
|
||||
pass
|
||||
except Exception as error:
|
||||
logger.error(f"{error.__class__.__name__}: {error}")
|
||||
return clients
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Service Implementation
|
||||
# ============================================================
|
||||
|
||||
|
||||
class Repository(GithubService):
|
||||
"""
|
||||
Repository service - fetches and stores repository data.
|
||||
|
||||
Key patterns:
|
||||
1. Inherits from GithubService
|
||||
2. Fetches all data in __init__ (eager loading)
|
||||
3. Stores data in attributes for check access
|
||||
4. Defines Pydantic models for data structures
|
||||
"""
|
||||
|
||||
def __init__(self, provider: "GithubProvider"):
|
||||
super().__init__(__class__.__name__, provider)
|
||||
# Fetch and store data during initialization
|
||||
self.repositories = self._list_repositories()
|
||||
|
||||
def _list_repositories(self) -> dict:
|
||||
"""List repositories based on provider scoping."""
|
||||
logger.info("Repository - Listing Repositories...")
|
||||
repos = {}
|
||||
|
||||
try:
|
||||
for client in self.clients:
|
||||
# Get repos from specified repositories
|
||||
for repo_name in self.provider.repositories:
|
||||
repo = client.get_repo(repo_name)
|
||||
self._process_repository(repo, repos)
|
||||
|
||||
# Get repos from specified organizations
|
||||
for org_name in self.provider.organizations:
|
||||
org = client.get_organization(org_name)
|
||||
for repo in org.get_repos():
|
||||
self._process_repository(repo, repos)
|
||||
except Exception as error:
|
||||
logger.error(f"{error.__class__.__name__}: {error}")
|
||||
|
||||
return repos
|
||||
|
||||
def _process_repository(self, repo, repos: dict):
|
||||
"""Process a single repository and add to repos dict."""
|
||||
repos[repo.id] = Repo(
|
||||
id=repo.id,
|
||||
name=repo.name,
|
||||
owner=repo.owner.login,
|
||||
full_name=repo.full_name,
|
||||
private=repo.private,
|
||||
archived=repo.archived,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Pydantic Models for Service Data
|
||||
# ============================================================
|
||||
|
||||
|
||||
class Repo(BaseModel):
|
||||
"""Model for GitHub Repository."""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
owner: str
|
||||
full_name: str
|
||||
private: bool
|
||||
archived: bool
|
||||
secret_scanning_enabled: Optional[bool] = None
|
||||
dependabot_enabled: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
# Make model hashable for use as dict key
|
||||
frozen = True
|
||||
@@ -0,0 +1,28 @@
|
||||
# Provider Documentation
|
||||
|
||||
## Local Documentation
|
||||
|
||||
For detailed provider development patterns, see:
|
||||
|
||||
### Core Documentation
|
||||
- `docs/developer-guide/provider.mdx` - Provider architecture and creation guide
|
||||
- `docs/developer-guide/services.mdx` - Adding services to existing providers
|
||||
|
||||
### Provider-Specific Details
|
||||
- `docs/developer-guide/aws-details.mdx` - AWS provider implementation
|
||||
- `docs/developer-guide/azure-details.mdx` - Azure provider implementation
|
||||
- `docs/developer-guide/gcp-details.mdx` - GCP provider implementation
|
||||
- `docs/developer-guide/kubernetes-details.mdx` - Kubernetes provider implementation
|
||||
- `docs/developer-guide/github-details.mdx` - GitHub provider implementation
|
||||
- `docs/developer-guide/m365-details.mdx` - Microsoft 365 provider implementation
|
||||
- `docs/developer-guide/alibabacloud-details.mdx` - Alibaba Cloud provider implementation
|
||||
- `docs/developer-guide/llm-details.mdx` - LLM provider implementation
|
||||
|
||||
## Contents
|
||||
|
||||
The documentation covers:
|
||||
- Provider types (SDK, API, Tool/Wrapper)
|
||||
- Provider class structure and identity
|
||||
- Service creation patterns
|
||||
- Client singleton implementation
|
||||
- Provider-specific authentication and API patterns
|
||||
Reference in New Issue
Block a user