feat(oci): Add Oracle Cloud Infrastructure provider with CIS 3.0 (#8893)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Sergio Garcia
2025-10-15 13:05:51 -04:00
committed by GitHub
parent 92efbe3926
commit c424707e32
380 changed files with 24223 additions and 18 deletions

5
.github/labeler.yml vendored
View File

@@ -42,6 +42,11 @@ provider/mongodbatlas:
- any-glob-to-any-file: "prowler/providers/mongodbatlas/**"
- any-glob-to-any-file: "tests/providers/mongodbatlas/**"
provider/oci:
- changed-files:
- any-glob-to-any-file: "prowler/providers/oraclecloud/**"
- any-glob-to-any-file: "tests/providers/oraclecloud/**"
github_actions:
- changed-files:
- any-glob-to-any-file: ".github/workflows/*"

View File

@@ -249,6 +249,21 @@ jobs:
run: |
poetry run pytest -n auto --cov=./prowler/providers/mongodbatlas --cov-report=xml:mongodb_atlas_coverage.xml tests/providers/mongodbatlas
# Test OCI
- name: OCI - Check if any file has changed
id: oci-changed-files
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47.0.0
with:
files: |
./prowler/providers/oraclecloud/**
./tests/providers/oraclecloud/**
./poetry.lock
- name: OCI - Test
if: steps.oci-changed-files.outputs.any_changed == 'true'
run: |
poetry run pytest -n auto --cov=./prowler/providers/oraclecloud --cov-report=xml:oci_coverage.xml tests/providers/oraclecloud
# Common Tests
- name: Lib - Test
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
@@ -268,4 +283,4 @@ jobs:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: prowler
files: ./aws_coverage.xml,./azure_coverage.xml,./gcp_coverage.xml,./kubernetes_coverage.xml,./github_coverage.xml,./nhn_coverage.xml,./m365_coverage.xml,./lib_coverage.xml,./config_coverage.xml
files: ./aws_coverage.xml,./azure_coverage.xml,./gcp_coverage.xml,./kubernetes_coverage.xml,./github_coverage.xml,./nhn_coverage.xml,./m365_coverage.xml,./oci_coverage.xml,./lib_coverage.xml,./config_coverage.xml

View File

@@ -88,6 +88,7 @@ prowler dashboard
| Kubernetes | 83 | 7 | 5 | 7 | Official | Stable | UI, API, CLI |
| GitHub | 17 | 2 | 1 | 0 | Official | Stable | UI, API, CLI |
| M365 | 70 | 7 | 3 | 2 | Official | Stable | UI, API, CLI |
| OCI | 51 | 13 | 1 | 10 | Official | Stable | CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | Beta | CLI |
| MongoDB Atlas | 10 | 3 | 0 | 0 | Official | Beta | CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | Beta | CLI |

View File

@@ -0,0 +1,41 @@
import warnings
from dashboard.common_methods import get_section_containers_cis
warnings.filterwarnings("ignore")
def get_table(data):
"""
Generate CIS OCI Foundations Benchmark v3.0 compliance table.
Args:
data: DataFrame containing compliance check results with columns:
- REQUIREMENTS_ID: CIS requirement ID (e.g., "1.1", "2.1")
- REQUIREMENTS_DESCRIPTION: Description of the requirement
- REQUIREMENTS_ATTRIBUTES_SECTION: CIS section name
- CHECKID: Prowler check identifier
- STATUS: Check status (PASS/FAIL)
- REGION: OCI region
- TENANCYID: OCI tenancy OCID
- RESOURCEID: Resource OCID or identifier
Returns:
Section containers organized by CIS sections for dashboard display
"""
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_DESCRIPTION",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"TENANCYID",
"RESOURCEID",
]
].copy()
return get_section_containers_cis(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION"
)

View File

@@ -224,6 +224,13 @@
"pages": [
"user-guide/providers/llm/getting-started-llm"
]
},
{
"group": "Oracle Cloud Infrastructure",
"pages": [
"user-guide/providers/oci/getting-started-oci",
"user-guide/providers/oci/authentication"
]
}
]
},

View File

@@ -288,3 +288,29 @@ prowler mongodbatlas --atlas-project-id <project_id>
```
See more details about MongoDB Atlas Authentication in [MongoDB Atlas Authentication](/user-guide/providers/mongodbatlas/authentication)
## Oracle Cloud
Prowler allows you to scan your Oracle Cloud deployments for security and compliance issues.
You have two options to authenticate:
1. OCI Config File Authentication: this config file can be generated using the OCI CLI with the `oci session authenticate` command or created manually using the OCI Console. For more details, see the [OCI Authentication Guide](/user-guide/providers/oci/authentication#oci-session-authentication).
```console
prowler oci
```
You can add different profiles to the config file to scan different tenancies or regions. In order to scan a specific profile, use the `--profile` flag:
```console
prowler oci --profile <profile_name>
```
2. Instance Principal Authentication: when running Prowler on an OCI Compute instance, you can use Instance Principal authentication. For more details, see the [OCI Authentication Guide](/user-guide/providers/oci/authentication#instance-principal-authentication).
```console
prowler oci --use-instance-principal
```
See more details about Oracle Cloud Authentication in [Oracle Cloud Authentication](/user-guide/providers/oci/authentication)

View File

@@ -46,6 +46,7 @@ The supported providers right now are:
| [Kubernetes](/user-guide/providers/kubernetes/in-cluster) | Official | UI, API, CLI |
| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | UI, API, CLI |
| [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI |

View File

@@ -0,0 +1,474 @@
---
title: 'Oracle Cloud Infrastructure (OCI) Authentication'
---
This guide covers all authentication methods supported by Prowler for Oracle Cloud Infrastructure (OCI).
## Authentication Methods
Prowler supports the following authentication methods for OCI:
1. **Config File Authentication** (using `~/.oci/config`)
- [OCI Session Authentication](#oci-session-authentication) **(Recommended)** - Automatically generates the config file via browser login
- [Manual API Key Setup](#setting-up-api-keys) - Manually create the config file with static API keys
2. [Instance Principal Authentication](#instance-principal-authentication) - For Prowler running inside OCI compute instances
3. [Environment Variables](#environment-variables) (Limited Support)
**Important Note:** OCI Session Authentication and Manual API Key Setup both use the same config file-based authentication method. The only difference is how the `~/.oci/config` file is generated:
- **Session Authentication**: Automatically created via browser login with temporary session tokens
- **Manual Setup**: You manually generate static API keys and create the config file
## OCI Session Authentication
**This is the recommended method for config file authentication** as it automatically generates the config file and doesn't require managing static API keys.
### Prerequisites
You need to have the **OCI CLI installed** to use session authentication.
For installation instructions, see the [OCI CLI Installation Guide](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm).
Verify your OCI CLI installation:
```bash
oci --version
```
### How It Works
The `oci session authenticate` command uses your browser to authenticate and creates temporary session tokens that are more secure than static API keys.
### Step 1: Authenticate with OCI Session
```bash
oci session authenticate
```
This command will:
1. Open your default browser
2. Redirect you to OCI Console login
3. Automatically create/update `~/.oci/config` with session tokens
4. Store session credentials securely
### Step 2: Add User OCID to Config File
After running `oci session authenticate`, you need to manually add your user OCID to the config file:
**Get your user OCID from the OCI Console:**
Navigate to: **Identity & Security** → **Users** → Click on your username → Copy the OCID
![Get User OCID from OCI Console](./images/oci-user-ocid.png)
Direct link: [OCI Console - Users](https://cloud.oracle.com/identity/domains/my-profile)
Or use the OCI CLI:
```bash
oci iam user list --all
```
Edit `~/.oci/config` and add the `user` parameter:
```ini
[DEFAULT]
region=us-ashburn-1
tenancy=ocid1.tenancy.oc1..aaaaaaaexample
fingerprint=11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11
key_file=/Users/yourusername/.oci/sessions/DEFAULT/oci_api_key.pem
security_token_file=/Users/yourusername/.oci/sessions/DEFAULT/token
user=ocid1.user.oc1..aaaaaaaexample # Add this line manually
```
### Step 3: Run Prowler
```bash
prowler oci
```
### Advantages of Session Authentication
- **No Manual Key Generation**: No need to generate RSA key pairs manually
- **Automatic Rotation**: Session tokens expire and can be refreshed
- **Browser-Based Login**: Uses your existing OCI Console credentials
- **More Secure**: Temporary credentials reduce the risk of long-term credential exposure
### Session Expiration
Session tokens typically expire after a period of time. When your session expires, simply run:
```bash
oci session authenticate
```
## Config File Authentication (Manual API Key Setup)
If you prefer to manually generate API keys instead of using browser-based session authentication, you can create the config file yourself with static API keys.
**Note:** This method uses the same `~/.oci/config` file as session authentication, but with static API keys instead of temporary session tokens.
### Default Configuration
By default, Prowler uses the OCI configuration file located at `~/.oci/config`.
**Config file structure:**
```ini
[DEFAULT]
user=ocid1.user.oc1..aaaaaaaexample
fingerprint=11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11
tenancy=ocid1.tenancy.oc1..aaaaaaaexample
region=us-ashburn-1
key_file=~/.oci/oci_api_key.pem
```
**Run Prowler:**
```bash
prowler oci
```
### Multiple Profiles
You can define multiple profiles in your config file:
```ini
[DEFAULT]
user=ocid1.user.oc1..user1
fingerprint=11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11
tenancy=ocid1.tenancy.oc1..tenancy1
region=us-ashburn-1
key_file=~/.oci/oci_api_key.pem
[PRODUCTION]
user=ocid1.user.oc1..user2
fingerprint=aa:bb:cc:dd:ee:ff:11:22:33:44:55:66:77:88:99:00
tenancy=ocid1.tenancy.oc1..tenancy2
region=us-phoenix-1
key_file=~/.oci/oci_api_key_prod.pem
[DEVELOPMENT]
user=ocid1.user.oc1..user3
fingerprint=99:88:77:66:55:44:33:22:11:00:ff:ee:dd:cc:bb:aa
tenancy=ocid1.tenancy.oc1..tenancy3
region=us-ashburn-1
key_file=~/.oci/oci_api_key_dev.pem
```
**Use a specific profile:**
```bash
prowler oci --profile PRODUCTION
```
### Custom Config File Path
Use a config file from a custom location:
```bash
prowler oci --config-file /path/to/custom/config
```
### Setting Up API Keys
#### Option A: Generate API Key Using OCI Console (Simpler)
1. Log in to OCI Console
2. Navigate to **Identity** → **Users** → Select your user
3. In the **Resources** section, click **API Keys**
4. Click **Add API Key**
5. Select **Generate API Key Pair**
6. Click **Download Private Key** - save this file as `~/.oci/oci_api_key.pem`
7. Click **Add**
8. The console will display a configuration file preview with:
- `user` OCID
- `fingerprint`
- `tenancy` OCID
- `region`
9. **Copy the entire configuration snippet** from the console and paste it into `~/.oci/config`
10. Add the `key_file` parameter pointing to your downloaded private key:
```ini
[DEFAULT]
user=ocid1.user.oc1..aaaaaaaexample
fingerprint=11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11
tenancy=ocid1.tenancy.oc1..aaaaaaaexample
region=us-ashburn-1
key_file=~/.oci/oci_api_key.pem # Add this line
```
11. Set proper permissions:
```bash
chmod 600 ~/.oci/oci_api_key.pem
chmod 600 ~/.oci/config
```
#### Option B: Generate API Key Manually
1. Generate the key pair locally:
```bash
mkdir -p ~/.oci
openssl genrsa -out ~/.oci/oci_api_key.pem 2048
chmod 600 ~/.oci/oci_api_key.pem
openssl rsa -pubout -in ~/.oci/oci_api_key.pem -out ~/.oci/oci_api_key_public.pem
```
2. Upload the public key to OCI Console:
- Navigate to **Identity** → **Users** → Select your user
- In the **Resources** section, click **API Keys**
- Click **Add API Key**
- Select **Paste Public Key** or **Choose Public Key File**
- Paste or upload the contents of `~/.oci/oci_api_key_public.pem`
- Click **Add**
3. The console will display the configuration file preview with your user OCID, fingerprint, tenancy OCID, and region.
4. Copy the configuration snippet from the console and create `~/.oci/config`:
```ini
[DEFAULT]
user=<user_ocid_from_console>
fingerprint=<fingerprint_from_console>
tenancy=<tenancy_ocid_from_console>
region=<region_from_console>
key_file=~/.oci/oci_api_key.pem
```
5. Set proper permissions:
```bash
chmod 600 ~/.oci/config
chmod 600 ~/.oci/oci_api_key.pem
```
#### Test Authentication
After setting up your API keys with either option, test the authentication:
```bash
prowler oci --list-checks
```
## Instance Principal Authentication
Instance Principal authentication allows OCI compute instances to authenticate without storing credentials.
**IMPORTANT:** This authentication method **only works when Prowler is running inside an OCI compute instance**. If you're running Prowler from your local machine or outside OCI, use [OCI Session Authentication](#oci-session-authentication) or [Config File Authentication](#config-file-authentication) instead.
### Prerequisites
1. **Prowler must be running on an OCI compute instance**
2. **Dynamic Group**: Create a dynamic group that includes your compute instance
3. **Policy**: Create policies granting the dynamic group access to resources
### Step 1: Create Dynamic Group
1. Navigate to **Identity** → **Dynamic Groups**
2. Click **Create Dynamic Group**
3. Enter a name (e.g., `prowler-instances`)
4. Add matching rule:
```
instance.compartment.id = 'ocid1.compartment.oc1..example'
```
Or for a specific instance:
```
instance.id = 'ocid1.instance.oc1..example'
```
### Step 2: Create Policies
Create a policy allowing the dynamic group to read resources:
```
Allow dynamic-group prowler-instances to inspect all-resources in tenancy
Allow dynamic-group prowler-instances to read all-resources in tenancy
Allow dynamic-group prowler-instances to read audit-events in tenancy
Allow dynamic-group prowler-instances to read cloud-guard-config in tenancy
```
### Step 3: Run Prowler with Instance Principal
On the compute instance, run:
```bash
prowler oci --use-instance-principal
```
### Use Cases for Instance Principal
- **Automated Security Scanning**: Run Prowler on a schedule using cron
- **CI/CD Pipelines**: Integrate security checks in build pipelines
- **Centralized Security Monitoring**: Deploy Prowler on a dedicated security instance
## Environment Variables
While OCI SDK supports environment variables, Prowler currently focuses on config file and instance principal authentication for better security and manageability.
If you need to use environment variables, they will be picked up by the OCI SDK:
```bash
export OCI_CLI_USER=ocid1.user.oc1..example
export OCI_CLI_FINGERPRINT=11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11
export OCI_CLI_TENANCY=ocid1.tenancy.oc1..example
export OCI_CLI_REGION=us-ashburn-1
export OCI_CLI_KEY_FILE=~/.oci/oci_api_key.pem
prowler oci
```
## Security Best Practices
### API Key Security
1. **Rotate API Keys Regularly**
- OCI recommends rotating API keys every 90 days
- Prowler includes a check for this: `identity_user_api_keys_rotated_90_days`
2. **Use Separate Keys Per Environment**
- Development, staging, and production should use different API keys
- Use profiles to manage multiple environments
3. **Restrict Key Permissions**
- Follow the principle of least privilege
- Grant only read permissions for security auditing
4. **Secure Key Storage**
```bash
chmod 600 ~/.oci/oci_api_key.pem
chmod 600 ~/.oci/config
```
5. **Never Commit Keys to Version Control**
- Add `~/.oci/` to `.gitignore`
- Use secret management systems for automation
### Instance Principal Security
1. **Use Specific Compartment Matching**
```
instance.compartment.id = 'specific-compartment-ocid'
```
Instead of:
```
ANY {instance.compartment.id = 'ocid1'}
```
2. **Scope Policies Appropriately**
- Grant access only to required resources
- Use compartment-level policies when possible
3. **Monitor Dynamic Group Membership**
- Regularly review which instances belong to security-related dynamic groups
- Use Cloud Guard to detect anomalous access patterns
## Troubleshooting
### Common Authentication Errors
#### Error: "ConfigFileNotFound"
**Cause**: OCI config file not found at default location
**Solution**:
```bash
# Check if config file exists
ls -la ~/.oci/config
# Create directory if missing
mkdir -p ~/.oci
# Specify custom location
prowler oci --config-file /path/to/config
```
#### Error: "InvalidKeyOrSignature"
**Cause**: Incorrect API key fingerprint or key file
**Solutions**:
1. Verify fingerprint matches OCI Console:
```bash
openssl rsa -pubout -outform DER -in ~/.oci/oci_api_key.pem | \
openssl md5 -c | \
awk '{print $2}'
```
2. Check key file path in config:
```ini
key_file=~/.oci/oci_api_key.pem # Use absolute path if needed
```
3. Verify key permissions:
```bash
chmod 600 ~/.oci/oci_api_key.pem
```
#### Error: "NotAuthenticated"
**Cause**: User OCID, tenancy OCID, or credentials incorrect
**Solutions**:
1. Verify OCIDs in OCI Console
2. Check that API key is uploaded to correct user
3. Ensure user has not been deleted or disabled
4. Test with OCI CLI:
```bash
oci iam region list
```
#### Error: "InstancePrincipalNotEnabled"
**Cause**: Instance Principal not configured correctly
**Solutions**:
1. Verify dynamic group includes your instance
2. Check policies grant required permissions
3. Ensure instance is in the correct compartment
4. Test with:
```bash
oci os ns get --auth instance_principal
```
### Permission Errors
**Error**: "Authorization failed or requested resource not found"
**Cause**: Insufficient IAM permissions
**Solution**: Add required policies (see [Required Permissions](./getting-started-oci.md#required-permissions))
### Configuration Validation
Validate your OCI configuration:
```bash
# Test OCI CLI connectivity
oci iam region list --profile DEFAULT
# Test with specific profile
oci iam region list --profile PRODUCTION
# Test instance principal
oci iam region list --auth instance_principal
```
## Testing Authentication
Before running a full Prowler scan, test authentication:
```bash
# List available checks (requires authentication)
prowler oci --list-checks
# List available services
prowler oci --list-services
# Test connection only
prowler oci --check identity_password_policy_minimum_length_14 --region us-ashburn-1
```
## Additional Resources
- [OCI SDK Configuration](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm)
- [OCI API Key Management](https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/managingcredentials.htm)
- [OCI Instance Principals](https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm)
- [OCI IAM Policies](https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/policygetstarted.htm)

View File

@@ -0,0 +1,376 @@
---
title: 'Getting Started with Oracle Cloud Infrastructure (OCI)'
---
Prowler supports security scanning of Oracle Cloud Infrastructure (OCI) environments. This guide will help you get started with using Prowler to audit your OCI tenancy.
## Prerequisites
Before you begin, ensure you have:
1. **Prowler installed** with OCI dependencies:
```bash
pip install prowler
# or for development:
poetry install
```
2. **OCI Python SDK** (automatically installed with Prowler):
```bash
pip install oci==2.152.1
```
3. **OCI Account Access** with appropriate permissions to read resources in your tenancy.
## Authentication
Prowler supports multiple authentication methods for OCI. For detailed authentication setup, see the [OCI Authentication Guide](./authentication.mdx).
**Note:** OCI Session Authentication and Config File Authentication both use the same `~/.oci/config` file. The difference is how the config file is generated - automatically via browser (session auth) or manually with API keys.
### Quick Start: OCI Session Authentication (Recommended)
The easiest and most secure method is using OCI session authentication, which automatically generates your config file via browser login.
**Prerequisites:** You need to have the **OCI CLI installed**. See the [OCI CLI Installation Guide](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm) for installation instructions.
1. Authenticate using the OCI CLI:
```bash
oci session authenticate
```
This will open your browser for OCI Console login and automatically generate the config file.
2. Add your user OCID to `~/.oci/config`:
**Get your user OCID from the OCI Console:**
Navigate to: **Identity & Security** → **Users** → Click on your username → Copy the OCID
![Get User OCID from OCI Console](./images/oci-user-ocid.png)
Direct link: [OCI Console - Users](https://cloud.oracle.com/identity/domains/my-profile)
Or use the OCI CLI:
```bash
oci iam user list --all
```
Edit `~/.oci/config` and add the `user` parameter:
```ini
[DEFAULT]
region=us-ashburn-1
tenancy=ocid1.tenancy.oc1..example
fingerprint=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
key_file=/Users/yourusername/.oci/sessions/DEFAULT/oci_api_key.pem
security_token_file=/Users/yourusername/.oci/sessions/DEFAULT/token
user=ocid1.user.oc1..example # Add this line
```
3. Run Prowler:
```bash
prowler oci
```
### Alternative: Manual API Key Setup
If you prefer to manually generate API keys instead of using browser-based session authentication, see the detailed instructions in the [Authentication Guide](./authentication.mdx#config-file-authentication-manual-api-key-setup).
**Note:** Both methods use the same `~/.oci/config` file - the difference is that manual setup uses static API keys while session authentication uses temporary session tokens.
#### Using a Specific Profile
If you have multiple profiles in your OCI config:
```bash
prowler oci --profile production
```
#### Using a Custom Config File
```bash
prowler oci --config-file /path/to/custom/config
```
### 2. Instance Principal Authentication
**IMPORTANT:** This authentication method **only works when Prowler is running inside an OCI compute instance**. If you're running Prowler from your local machine, use [OCI Session Authentication](#quick-start-oci-session-authentication-recommended) instead.
When running Prowler on an OCI Compute instance, you can use Instance Principal authentication:
```bash
prowler oci --use-instance-principal
```
**Requirements:**
- **Prowler must be running on an OCI compute instance**
- The compute instance must have a dynamic group and policy allowing access to OCI resources
- Example policy:
```
Allow dynamic-group prowler-instances to inspect all-resources in tenancy
Allow dynamic-group prowler-instances to read all-resources in tenancy
```
## Basic Usage
### Scan Entire Tenancy
```bash
prowler oci
```
### Scan Specific Region
```bash
prowler oci --region us-phoenix-1
```
### Scan Specific Compartments
```bash
prowler oci --compartment-id ocid1.compartment.oc1..example1 ocid1.compartment.oc1..example2
```
### Run Specific Checks
```bash
prowler oci --check identity_password_policy_minimum_length_14
```
### Run Specific Services
```bash
prowler oci --service identity network
```
### Compliance Frameworks
Run CIS OCI Foundations Benchmark v3.0:
```bash
prowler oci --compliance cis_3.0_oci
```
## Required Permissions
Prowler requires **read-only** permissions to audit your OCI tenancy. Below are the minimum required permissions:
### Tenancy-Level Policy
Create a group `prowler-users` and add your user to it, then create this policy:
```
Allow group prowler-users to inspect all-resources in tenancy
Allow group prowler-users to read all-resources in tenancy
Allow group prowler-users to read audit-events in tenancy
Allow group prowler-users to read cloud-guard-config in tenancy
Allow group prowler-users to read cloud-guard-problems in tenancy
Allow group prowler-users to read cloud-guard-targets in tenancy
```
### Service-Specific Permissions
For more granular control, you can grant specific permissions:
```
# Identity
Allow group prowler-users to inspect users in tenancy
Allow group prowler-users to inspect groups in tenancy
Allow group prowler-users to inspect policies in tenancy
Allow group prowler-users to inspect authentication-policies in tenancy
Allow group prowler-users to inspect dynamic-groups in tenancy
# Networking
Allow group prowler-users to inspect vcns in tenancy
Allow group prowler-users to inspect subnets in tenancy
Allow group prowler-users to inspect security-lists in tenancy
Allow group prowler-users to inspect network-security-groups in tenancy
Allow group prowler-users to inspect route-tables in tenancy
Allow group prowler-users to inspect dhcp-options in tenancy
Allow group prowler-users to inspect internet-gateways in tenancy
Allow group prowler-users to inspect nat-gateways in tenancy
Allow group prowler-users to inspect service-gateways in tenancy
# Compute
Allow group prowler-users to inspect instances in tenancy
Allow group prowler-users to inspect instance-configurations in tenancy
Allow group prowler-users to inspect boot-volumes in tenancy
Allow group prowler-users to inspect volume-attachments in tenancy
# Storage
Allow group prowler-users to inspect buckets in tenancy
Allow group prowler-users to inspect volumes in tenancy
Allow group prowler-users to inspect file-systems in tenancy
# Database
Allow group prowler-users to inspect autonomous-databases in tenancy
Allow group prowler-users to inspect db-systems in tenancy
# Keys Management
Allow group prowler-users to inspect vaults in tenancy
Allow group prowler-users to inspect keys in tenancy
# Monitoring & Events
Allow group prowler-users to read metrics in tenancy
Allow group prowler-users to inspect alarms in tenancy
Allow group prowler-users to inspect ons-topics in tenancy
Allow group prowler-users to inspect ons-subscriptions in tenancy
Allow group prowler-users to inspect rules in tenancy
```
## Output Formats
Prowler supports multiple output formats for OCI:
### JSON
```bash
prowler oci --output-formats json
```
### CSV
```bash
prowler oci --output-formats csv
```
### HTML
```bash
prowler oci --output-formats html
```
### Multiple Formats
```bash
prowler oci --output-formats json csv html
```
## Common Scenarios
### Security Assessment
Full security assessment with CIS compliance:
```bash
prowler oci \
--compliance cis_3.0_oci \
--output-formats json html \
--output-directory ./oci-assessment-$(date +%Y%m%d)
```
### Continuous Monitoring
Run specific security-critical checks:
```bash
prowler oci \
--check identity_user_mfa_enabled_console_access \
network_security_list_ingress_from_internet_to_ssh_port \
objectstorage_bucket_not_publicly_accessible \
--output-formats json
```
### Compartment-Specific Audit
Audit a specific project compartment:
```bash
prowler oci \
--compartment-id ocid1.compartment.oc1..projecta \
--profile production \
--region us-ashburn-1
```
## Troubleshooting
### Authentication Issues
**Error: "Could not find a valid config file"**
- Ensure `~/.oci/config` exists and is properly formatted
- Verify the path to your API key is correct
- Check file permissions: `chmod 600 ~/.oci/config ~/.oci/oci_api_key.pem`
**Error: "Invalid key or signature"**
- Verify the API key fingerprint matches the one in OCI Console
- Ensure the public key is uploaded to your OCI user account
- Check that the private key file is accessible
### Permission Issues
**Error: "Authorization failed or requested resource not found"**
- Verify your user has the required policies (see [Required Permissions](#required-permissions))
- Check that policies apply to the correct compartments
- Ensure policies are not restricted by conditions that exclude your user
### Region Issues
**Error: "Invalid region"**
- Check available regions: `prowler oci --list-regions`
- Verify your tenancy is subscribed to the region
- Use the region identifier (e.g., `us-ashburn-1`), not the display name
## Advanced Usage
### Using Mutelist
Create a mutelist file to suppress specific findings:
```yaml
# oci-mutelist.yaml
Tenancies:
- "ocid1.tenancy.oc1..example":
Checks:
"identity_password_policy_*":
Regions:
- "us-ashburn-1"
Resources:
- "ocid1.user.oc1..example"
```
Run with mutelist:
```bash
prowler oci --mutelist-file oci-mutelist.yaml
```
### Custom Checks Metadata
Override check metadata:
```yaml
# custom-metadata.yaml
identity_user_mfa_enabled_console_access:
Severity: critical
CheckTitle: "Custom: Ensure MFA is enabled for all console users"
```
Run with custom metadata:
```bash
prowler oci --custom-checks-metadata-file custom-metadata.yaml
```
### Filtering by Status
Only show failed checks:
```bash
prowler oci --status FAIL
```
### Filtering by Severity
Only show critical and high severity findings:
```bash
prowler oci --severity critical high
```
## Next Steps
- Learn about [Compliance Frameworks](/user-guide/cli/tutorials/compliance) in Prowler
- Review [Prowler Output Formats](/user-guide/cli/tutorials/reporting)
- Explore [Integrations](/user-guide/cli/tutorials/integrations) with SIEM and ticketing systems
## Additional Resources
- [OCI Documentation](https://docs.oracle.com/en-us/iaas/Content/home.htm)
- [CIS OCI Foundations Benchmark](https://www.cisecurity.org/benchmark/oracle_cloud)
- [Prowler Documentation](https://docs.prowler.com)
- [Prowler GitHub](https://github.com/prowler-cloud/prowler)

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

63
poetry.lock generated
View File

@@ -1222,6 +1222,18 @@ files = [
{file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
]
[[package]]
name = "circuitbreaker"
version = "2.1.3"
description = "Python Circuit Breaker pattern implementation"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "circuitbreaker-2.1.3-py3-none-any.whl", hash = "sha256:87ba6a3ed03fdc7032bc175561c2b04d52ade9d5faf94ca2b035fbdc5e6b1dd1"},
{file = "circuitbreaker-2.1.3.tar.gz", hash = "sha256:1a4baee510f7bea3c91b194dcce7c07805fe96c4423ed5594b75af438531d084"},
]
[[package]]
name = "click"
version = "8.1.8"
@@ -2404,8 +2416,6 @@ python-versions = "*"
groups = ["dev"]
files = [
{file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"},
{file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"},
{file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"},
]
[package.dependencies]
@@ -3459,6 +3469,29 @@ rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "oci"
version = "2.160.3"
description = "Oracle Cloud Infrastructure Python SDK"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "oci-2.160.3-py3-none-any.whl", hash = "sha256:858bff3e697098bdda44833d2476bfb4632126f0182178e7dbde4dbd156d71f0"},
{file = "oci-2.160.3.tar.gz", hash = "sha256:57514889be3b713a8385d86e3ba8a33cf46e3563c2a7e29a93027fb30b8a2537"},
]
[package.dependencies]
certifi = "*"
circuitbreaker = {version = ">=1.3.1,<3.0.0", markers = "python_version >= \"3.7\""}
cryptography = ">=3.2.1,<46.0.0"
pyOpenSSL = ">=17.5.0,<25.0.0"
python-dateutil = ">=2.5.3,<3.0.0"
pytz = ">=2016.10"
[package.extras]
adk = ["docstring-parser (>=0.16) ; python_version >= \"3.10\" and python_version < \"4\"", "mcp (>=1.6.0) ; python_version >= \"3.10\" and python_version < \"4\"", "pydantic (>=2.10.6) ; python_version >= \"3.10\" and python_version < \"4\"", "rich (>=13.9.4) ; python_version >= \"3.10\" and python_version < \"4\""]
[[package]]
name = "openapi-schema-validator"
version = "0.6.3"
@@ -4338,6 +4371,25 @@ cffi = ">=1.4.1"
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pyopenssl"
version = "24.3.0"
description = "Python wrapper module around the OpenSSL library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"},
{file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"},
]
[package.dependencies]
cryptography = ">=41.0.5,<45"
[package.extras]
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"]
test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"]
[[package]]
name = "pyparsing"
version = "3.2.3"
@@ -5033,7 +5085,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"},
@@ -5042,7 +5093,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"},
{file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"},
@@ -5051,7 +5101,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"},
{file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"},
@@ -5060,7 +5109,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"},
{file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"},
@@ -5069,7 +5117,6 @@ files = [
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"},
{file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"},
{file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"},
@@ -5898,4 +5945,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">3.9.1,<3.13"
content-hash = "890d165dc90871b6c2f34a31c61f5857ade538cc62fe33f024a2f57e1c5ac1b1"
content-hash = "c2fb8567f1a6be319ae73f8c3a30e7b5be6f6fc65deee567d2a9e09eadd984c9"

View File

@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add C5 compliance framework for the AWS provider [(#8830)](https://github.com/prowler-cloud/prowler/pull/8830)
- Equality validation for CheckID, filename and classname [(#8690)](https://github.com/prowler-cloud/prowler/pull/8690)
- Improve logging for Security Hub integration [(#8608)](https://github.com/prowler-cloud/prowler/pull/8608)
- Oracle Cloud provider with CIS 3.0 benchmark [(#8893)](https://github.com/prowler-cloud/prowler/pull/8893)
- Support for Atlassian Document Format (ADF) in Jira integration [(#8878)](https://github.com/prowler-cloud/prowler/pull/8878)
- Add Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000)

View File

@@ -59,6 +59,7 @@ from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
from prowler.lib.outputs.compliance.cis.cis_oci import OCICIS
from prowler.lib.outputs.compliance.compliance import display_compliance_table
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
@@ -111,6 +112,7 @@ from prowler.providers.llm.models import LLMOutputOptions
from prowler.providers.m365.models import M365OutputOptions
from prowler.providers.mongodbatlas.models import MongoDBAtlasOutputOptions
from prowler.providers.nhn.models import NHNOutputOptions
from prowler.providers.oraclecloud.models import OCIOutputOptions
def prowler():
@@ -330,6 +332,10 @@ def prowler():
output_options = IACOutputOptions(args, bulk_checks_metadata)
elif provider == "llm":
output_options = LLMOutputOptions(args, bulk_checks_metadata)
elif provider == "oci":
output_options = OCIOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
# Run the quick inventory for the provider if available
if hasattr(args, "quick_inventory") and args.quick_inventory:
@@ -931,6 +937,34 @@ def prowler():
generated_outputs["compliance"].append(generic_compliance)
generic_compliance.batch_write_data_to_file()
elif provider == "oci":
for compliance_name in input_compliance_frameworks:
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = OCICIS(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(cis)
cis.batch_write_data_to_file()
else:
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
file_path=filename,
)
generated_outputs["compliance"].append(generic_compliance)
generic_compliance.batch_write_data_to_file()
# AWS Security Hub Integration
if provider == "aws":
# Send output to S3 if needed (-B / -D) for all the output formats

View File

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ class Provider(str, Enum):
IAC = "iac"
NHN = "nhn"
MONGODBATLAS = "mongodbatlas"
OCI = "oci"
# Compliance

View File

@@ -0,0 +1,61 @@
### Tenancy, Check and/or Region can be * to apply for all the cases.
### Tenancy == OCI Tenancy OCID and Region == OCI Region
### Resources and tags are lists that can have either Regex or Keywords.
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
### For each check you can except Tenancies, Regions, Resources and/or Tags.
########################### MUTELIST EXAMPLE ###########################
Mutelist:
Tenancies:
"ocid1.tenancy.oc1..aaaaaaaexample":
Checks:
"iam_user_mfa_enabled":
Regions:
- "us-phoenix-1"
Resources:
- "ocid1.user.oc1..aaaaaaaexample1" # Will ignore user1 in check iam_user_mfa_enabled
- "ocid1.user.oc1..aaaaaaaexample2" # Will ignore user2 in check iam_user_mfa_enabled
Description: "Check iam_user_mfa_enabled muted for region us-phoenix-1 and specific user resources"
"objectstorage_*":
Regions:
- "*"
Resources:
- "*" # Will ignore every Object Storage check in every region
"*":
Regions:
- "*"
Resources:
- "test"
Tags:
- "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and
- "project=test|project=stage" # either of ('project=test' OR project=stage) in tenancy ocid1.tenancy.oc1..aaaaaaaexample and every region
- "environment=prod" # Will ignore every resource containing the string "test" and tag environment=prod
"*":
Checks:
"objectstorage_bucket_public_access":
Regions:
- "us-ashburn-1"
- "us-phoenix-1"
Resources:
- "ci-logs" # Will ignore bucket "ci-logs" AND ALSO bucket "ci-logs-replica" in specified check and regions
- "logs" # Will ignore EVERY BUCKET containing the string "logs" in specified check and regions
- ".+-logs" # Will ignore all buckets containing the terms ci-logs, qa-logs, etc. in specified check and regions
"*":
Regions:
- "*"
Resources:
- "*"
Tags:
- "environment=dev" # Will ignore every resource containing the tag 'environment=dev' in every tenancy and region
"compute_instance_monitoring_enabled":
Regions:
- "*"
Resources:
- "*"
Exceptions:
Tenancies:
- "ocid1.tenancy.oc1..aaaaaaaexample2"
Regions:
- "eu-frankfurt-1"
- "eu-amsterdam-1" # Will ignore every resource in check compute_instance_monitoring_enabled except the ones in tenancy ocid1.tenancy.oc1..aaaaaaaexample2 located in eu-frankfurt-1 or eu-amsterdam-1

View File

@@ -518,8 +518,16 @@ def execute_checks(
)
try:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # oci SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
global_provider.type, global_provider.type
)
# Import check module
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
check_module_path = f"prowler.providers.{provider_directory}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)

View File

@@ -599,6 +599,46 @@ class Check_Report_GCP(Check_Report):
)
@dataclass
class Check_Report_OCI(Check_Report):
"""Contains the OCI Check's finding information."""
resource_name: str
resource_id: str
compartment_id: str
region: str
def __init__(
self,
metadata: Dict,
resource: Any,
region: str = None,
resource_name: str = None,
resource_id: str = None,
compartment_id: str = None,
) -> None:
"""Initialize the OCI Check's finding information.
Args:
metadata: The metadata of the check.
resource: Basic information about the resource. Defaults to None.
region: The region of the resource.
resource_name: The name of the resource related with the finding.
resource_id: The OCID of the resource related with the finding.
compartment_id: The compartment OCID of the resource.
"""
super().__init__(metadata, resource)
self.resource_id = (
resource_id
or getattr(resource, "id", None)
or getattr(resource, "name", None)
or ""
)
self.resource_name = resource_name or getattr(resource, "name", "")
self.compartment_id = compartment_id or getattr(resource, "compartment_id", "")
self.region = region or getattr(resource, "region", "")
@dataclass
class Check_Report_Kubernetes(Check_Report):
# TODO change class name to CheckReportKubernetes

View File

@@ -46,8 +46,14 @@ def recover_checks_from_provider(
# List all available modules in the selected provider and service
def list_modules(provider: str, service: str):
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(provider, provider)
# This module path requires the full path including "prowler."
module_path = f"prowler.providers.{provider}.services"
module_path = f"prowler.providers.{provider_directory}.services"
if service:
module_path += f".{service}"
return walk_packages(

View File

@@ -26,16 +26,17 @@ class ProwlerArgumentParser:
self.parser = argparse.ArgumentParser(
prog="prowler",
formatter_class=RawTextHelpFormatter,
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,dashboard,iac} ...",
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,github,nhn,mongodbatlas,oci,dashboard,iac} ...",
epilog="""
Available Cloud Providers:
{aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas}
{aws,azure,gcp,kubernetes,m365,github,iac,llm,nhn,mongodbatlas,oci}
aws AWS Provider
azure Azure Provider
gcp GCP Provider
kubernetes Kubernetes Provider
m365 Microsoft 365 Provider
github GitHub Provider
oci Oracle Cloud Infrastructure Provider
iac IaC Provider (Beta)
llm LLM Provider (Beta)
nhn NHN Provider (Unofficial)

View File

@@ -0,0 +1,106 @@
from prowler.config.config import timestamp
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.cis.models import OCICISModel
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.finding import Finding
class OCICIS(ComplianceOutput):
"""
This class represents the OCI CIS compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into OCI CIS compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into OCI CIS compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = OCICISModel(
Provider=finding.provider,
Description=compliance.Description,
TenancyId=finding.account_uid,
Region=finding.region,
AssessmentDate=str(timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_Profile=attribute.Profile,
Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus,
Requirements_Attributes_Description=attribute.Description,
Requirements_Attributes_RationaleStatement=attribute.RationaleStatement,
Requirements_Attributes_ImpactStatement=attribute.ImpactStatement,
Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure,
Requirements_Attributes_AuditProcedure=attribute.AuditProcedure,
Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation,
Requirements_Attributes_DefaultValue=attribute.DefaultValue,
Requirements_Attributes_References=attribute.References,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
Framework=compliance.Framework,
Name=compliance.Name,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = OCICISModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
TenancyId="",
Region="",
AssessmentDate=str(timestamp),
Requirements_Id=requirement.Id,
Requirements_Description=requirement.Description,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_SubSection=attribute.SubSection,
Requirements_Attributes_Profile=attribute.Profile,
Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus,
Requirements_Attributes_Description=attribute.Description,
Requirements_Attributes_RationaleStatement=attribute.RationaleStatement,
Requirements_Attributes_ImpactStatement=attribute.ImpactStatement,
Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure,
Requirements_Attributes_AuditProcedure=attribute.AuditProcedure,
Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation,
Requirements_Attributes_DefaultValue=attribute.DefaultValue,
Requirements_Attributes_References=attribute.References,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
Framework=compliance.Framework,
Name=compliance.Name,
)
self._data.append(compliance_row)

View File

@@ -207,6 +207,50 @@ class GithubCISModel(BaseModel):
Name: str
class OCICISModel(BaseModel):
"""
OCICISModel generates a finding's output in OCI CIS Compliance format.
"""
Provider: str
Description: str
TenancyId: str
Region: str
AssessmentDate: str
Requirements_Id: str
Requirements_Description: str
Requirements_Attributes_Section: str
Requirements_Attributes_SubSection: Optional[str] = None
Requirements_Attributes_Profile: str
Requirements_Attributes_AssessmentStatus: str
Requirements_Attributes_Description: str
Requirements_Attributes_RationaleStatement: str
Requirements_Attributes_ImpactStatement: str
Requirements_Attributes_RemediationProcedure: str
Requirements_Attributes_AuditProcedure: str
Requirements_Attributes_AdditionalInformation: str
Requirements_Attributes_DefaultValue: Optional[str] = None
Requirements_Attributes_References: str
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool
Framework: str
Name: str
# Compliance models alias for backwards compatibility
CIS_AWS = AWSCISModel
CIS_Azure = AzureCISModel
CIS_GCP = GCPCISModel
CIS_Kubernetes = KubernetesCISModel
CIS_M365 = M365CISModel
CIS_Github = GithubCISModel
CIS_OCI = OCICISModel
# TODO: Create a parent class for the common fields of CIS and have the specific classes from each provider to inherit from it.
# It is not done yet because it is needed to respect the current order of the fields in the output file.

View File

@@ -323,6 +323,20 @@ class Finding(BaseModel):
output_data["resource_uid"] = check_output.model
output_data["region"] = check_output.model
elif provider.type == "oci":
output_data["auth_method"] = (
f"Profile: {get_nested_attribute(provider, 'session.profile')}"
)
output_data["account_uid"] = get_nested_attribute(
provider, "identity.tenancy_id"
)
output_data["account_name"] = get_nested_attribute(
provider, "identity.tenancy_name"
)
output_data["resource_name"] = check_output.resource_name
output_data["resource_uid"] = check_output.resource_id
output_data["region"] = check_output.region
# check_output Unique ID
# TODO: move this to a function
# TODO: in Azure, GCP and K8s there are findings without resource_name

View File

@@ -973,6 +973,53 @@ class HTML(Output):
)
return ""
@staticmethod
def get_oci_assessment_summary(provider: Provider) -> str:
"""
get_oci_assessment_summary gets the HTML assessment summary for the OCI provider
Args:
provider (Provider): the OCI provider object
Returns:
str: HTML assessment summary for the OCI provider
"""
try:
profile = getattr(provider.session, "profile", "default")
tenancy_name = getattr(provider.identity, "tenancy_name", "unknown")
tenancy_id = getattr(provider.identity, "tenancy_id", "unknown")
return f"""
<div class="col-md-2">
<div class="card">
<div class="card-header">
OCI Assessment Summary
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>OCI Tenancy:</b> {tenancy_name if tenancy_name != "unknown" else tenancy_id}
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
OCI Credentials
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<b>Profile:</b> {profile}
</li>
</ul>
</div>
</div>"""
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
return ""
@staticmethod
def get_assessment_summary(provider: Provider) -> str:
"""

View File

@@ -28,6 +28,8 @@ def stdout_report(finding, color, verbose, status, fix):
details = finding.check_metadata.CheckID
if finding.check_metadata.Provider == "iac":
details = finding.check_metadata.CheckID
if finding.check_metadata.Provider == "oci":
details = finding.region
if (verbose or fix) and (not status or finding.status in status):
if finding.muted:

View File

@@ -67,6 +67,13 @@ def display_summary_table(
elif provider.type == "llm":
entity_type = "LLM"
audited_entities = provider.model
elif provider.type == "oci":
entity_type = "Tenancy"
audited_entities = (
provider.identity.tenancy_name
if provider.identity.tenancy_name != "unknown"
else provider.identity.tenancy_id
)
# Check if there are findings and that they are not all MANUAL
if findings and not all(finding.status == "MANUAL" for finding in findings):

View File

@@ -16,9 +16,15 @@ def init_providers_parser(self):
providers = Provider.get_available_providers()
for provider in providers:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(provider, provider)
getattr(
import_module(
f"{providers_path}.{provider}.{provider_arguments_lib_path}"
f"{providers_path}.{provider_directory}.{provider_arguments_lib_path}"
),
init_provider_arguments_function,
)(self)
@@ -32,10 +38,18 @@ def init_providers_parser(self):
def validate_provider_arguments(arguments: Namespace) -> tuple[bool, str]:
"""validate_provider_arguments returns {True, "} if the provider arguments passed are valid and can be used together"""
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # OCI SDK conflict avoidance
}
provider_directory = provider_directory_map.get(
arguments.provider, arguments.provider
)
# Provider function must be located at prowler.providers.<provider>.lib.arguments.arguments.validate_arguments
return getattr(
import_module(
f"{providers_path}.{arguments.provider}.{provider_arguments_lib_path}"
f"{providers_path}.{provider_directory}.{provider_arguments_lib_path}"
),
validate_provider_arguments_function,
)(arguments)

View File

@@ -146,8 +146,24 @@ class Provider(ABC):
@staticmethod
def init_global_provider(arguments: Namespace) -> None:
try:
# Map CLI provider names to directory names (for cases where they differ)
provider_directory_map = {
"oci": "oraclecloud", # oci SDK conflict avoidance
}
# Map CLI provider names to provider file names (for cases where they differ)
provider_file_map = {
"oci": "oci", # oraclecloud directory but oci_provider.py file
}
provider_directory = provider_directory_map.get(
arguments.provider, arguments.provider
)
provider_file = provider_file_map.get(
arguments.provider, arguments.provider
)
provider_class_path = (
f"{providers_path}.{arguments.provider}.{arguments.provider}_provider"
f"{providers_path}.{provider_directory}.{provider_file}_provider"
)
provider_class_name = f"{arguments.provider.capitalize()}Provider"
provider_class = getattr(
@@ -275,6 +291,17 @@ class Provider(ABC):
mutelist_path=arguments.mutelist_file,
fixer_config=fixer_config,
)
elif "oci" in provider_class_name.lower():
provider_class(
oci_config_file=arguments.oci_config_file,
profile=arguments.profile,
region=arguments.region,
compartment_ids=arguments.compartment_id,
config_path=arguments.config_file,
mutelist_path=arguments.mutelist_file,
fixer_config=fixer_config,
use_instance_principal=arguments.use_instance_principal,
)
except TypeError as error:
logger.critical(

View File

@@ -0,0 +1,61 @@
"""OCI Provider Configuration Constants"""
# Default OCI Configuration
OCI_DEFAULT_CONFIG_FILE = "~/.oci/config"
OCI_DEFAULT_PROFILE = "DEFAULT"
# OCI Session Configuration
OCI_MAX_RETRIES = 3
OCI_TIMEOUT = 60
# OCI User Agent
OCI_USER_AGENT = "Prowler"
# OCI Regions - Commercial Regions
OCI_COMMERCIAL_REGIONS = {
"ap-chuncheon-1": "South Korea Central (Chuncheon)",
"ap-hyderabad-1": "India West (Hyderabad)",
"ap-melbourne-1": "Australia Southeast (Melbourne)",
"ap-mumbai-1": "India West (Mumbai)",
"ap-osaka-1": "Japan Central (Osaka)",
"ap-seoul-1": "South Korea North (Seoul)",
"ap-singapore-1": "Singapore (Singapore)",
"ap-sydney-1": "Australia East (Sydney)",
"ap-tokyo-1": "Japan East (Tokyo)",
"ca-montreal-1": "Canada Southeast (Montreal)",
"ca-toronto-1": "Canada Southeast (Toronto)",
"eu-amsterdam-1": "Netherlands Northwest (Amsterdam)",
"eu-frankfurt-1": "Germany Central (Frankfurt)",
"eu-madrid-1": "Spain Central (Madrid)",
"eu-marseille-1": "France South (Marseille)",
"eu-milan-1": "Italy Northwest (Milan)",
"eu-paris-1": "France Central (Paris)",
"eu-stockholm-1": "Sweden Central (Stockholm)",
"eu-zurich-1": "Switzerland North (Zurich)",
"il-jerusalem-1": "Israel Central (Jerusalem)",
"me-abudhabi-1": "UAE East (Abu Dhabi)",
"me-dubai-1": "UAE East (Dubai)",
"me-jeddah-1": "Saudi Arabia West (Jeddah)",
"mx-monterrey-1": "Mexico Northeast (Monterrey)",
"mx-queretaro-1": "Mexico Central (Queretaro)",
"sa-bogota-1": "Colombia (Bogota)",
"sa-santiago-1": "Chile (Santiago)",
"sa-saopaulo-1": "Brazil East (Sao Paulo)",
"sa-valparaiso-1": "Chile West (Valparaiso)",
"sa-vinhedo-1": "Brazil Southeast (Vinhedo)",
"uk-cardiff-1": "UK West (Cardiff)",
"uk-london-1": "UK South (London)",
"us-ashburn-1": "US East (Ashburn)",
"us-chicago-1": "US East (Chicago)",
"us-phoenix-1": "US West (Phoenix)",
"us-sanjose-1": "US West (San Jose)",
}
# OCI Government Regions
OCI_GOVERNMENT_REGIONS = {
"us-langley-1": "US Gov West",
"us-luke-1": "US Gov East",
}
# All OCI Regions
OCI_REGIONS = {**OCI_COMMERCIAL_REGIONS, **OCI_GOVERNMENT_REGIONS}

View File

@@ -0,0 +1,197 @@
from prowler.exceptions.exceptions import ProwlerException
# Exceptions codes from 7000 to 7999 are reserved for OCI exceptions
class OCIBaseException(ProwlerException):
"""Base class for OCI errors."""
OCI_ERROR_CODES = {
(7000, "OCIClientError"): {
"message": "OCI ClientError occurred",
"remediation": "Check your OCI client configuration and permissions.",
},
(7001, "OCIConfigFileNotFoundError"): {
"message": "OCI Config file not found",
"remediation": "Ensure the OCI config file exists at the specified path, please visit https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm",
},
(7002, "OCIInvalidConfigError"): {
"message": "Invalid OCI configuration",
"remediation": "Verify that your OCI configuration is properly set up, please visit https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm",
},
(7003, "OCIProfileNotFoundError"): {
"message": "OCI Profile not found",
"remediation": "Ensure the OCI profile exists in your config file, please visit https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm",
},
(7004, "OCINoCredentialsError"): {
"message": "No OCI credentials found",
"remediation": "Verify that OCI credentials are properly set up, please visit https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm",
},
(7005, "OCIAuthenticationError"): {
"message": "OCI authentication failed",
"remediation": "Check your OCI credentials and ensure they are valid.",
},
(7006, "OCISetUpSessionError"): {
"message": "OCI session setup error",
"remediation": "Check the OCI session setup and ensure it is properly configured.",
},
(7007, "OCIInvalidRegionError"): {
"message": "Invalid OCI region",
"remediation": "Check the OCI region name and ensure it is valid, please visit https://docs.oracle.com/en-us/iaas/Content/General/Concepts/regions.htm",
},
(7008, "OCIInvalidCompartmentError"): {
"message": "Invalid OCI compartment",
"remediation": "Check the OCI compartment OCID and ensure it exists and is accessible.",
},
(7009, "OCIInvalidTenancyError"): {
"message": "Invalid OCI tenancy",
"remediation": "Check the OCI tenancy OCID and ensure it is valid.",
},
(7010, "OCIServiceError"): {
"message": "OCI service error occurred",
"remediation": "Check the OCI service error details and ensure proper permissions.",
},
(7011, "OCIInstancePrincipalError"): {
"message": "OCI instance principal authentication failed",
"remediation": "Ensure the instance has proper instance principal configuration and dynamic group policies.",
},
(7012, "OCIInvalidOCIDError"): {
"message": "Invalid OCI OCID format",
"remediation": "Check the OCID format and ensure it matches the pattern: ocid1.<resource_type>.<realm>.<region>.<unique_id>",
},
}
def __init__(self, code, file=None, original_exception=None, message=None):
error_info = self.OCI_ERROR_CODES.get((code, self.__class__.__name__))
if message:
error_info["message"] = message
super().__init__(
code,
source="OCI",
file=file,
original_exception=original_exception,
error_info=error_info,
)
class OCICredentialsError(OCIBaseException):
"""Base class for OCI credentials errors."""
def __init__(self, code, file=None, original_exception=None, message=None):
super().__init__(code, file, original_exception, message)
class OCIClientError(OCICredentialsError):
"""Exception raised when OCI client error occurs."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7000, file=file, original_exception=original_exception, message=message
)
class OCIConfigFileNotFoundError(OCICredentialsError):
"""Exception raised when OCI config file is not found."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7001, file=file, original_exception=original_exception, message=message
)
class OCIInvalidConfigError(OCICredentialsError):
"""Exception raised when OCI configuration is invalid."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7002, file=file, original_exception=original_exception, message=message
)
class OCIProfileNotFoundError(OCICredentialsError):
"""Exception raised when OCI profile is not found."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7003, file=file, original_exception=original_exception, message=message
)
class OCINoCredentialsError(OCICredentialsError):
"""Exception raised when no OCI credentials are found."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7004, file=file, original_exception=original_exception, message=message
)
class OCIAuthenticationError(OCICredentialsError):
"""Exception raised when OCI authentication fails."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7005, file=file, original_exception=original_exception, message=message
)
class OCISetUpSessionError(OCIBaseException):
"""Exception raised when OCI session setup fails."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7006, file=file, original_exception=original_exception, message=message
)
class OCIInvalidRegionError(OCIBaseException):
"""Exception raised when OCI region is invalid."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7007, file=file, original_exception=original_exception, message=message
)
class OCIInvalidCompartmentError(OCIBaseException):
"""Exception raised when OCI compartment is invalid."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7008, file=file, original_exception=original_exception, message=message
)
class OCIInvalidTenancyError(OCIBaseException):
"""Exception raised when OCI tenancy is invalid."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7009, file=file, original_exception=original_exception, message=message
)
class OCIServiceError(OCIBaseException):
"""Exception raised when OCI service error occurs."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7010, file=file, original_exception=original_exception, message=message
)
class OCIInstancePrincipalError(OCIBaseException):
"""Exception raised when OCI instance principal authentication fails."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7011, file=file, original_exception=original_exception, message=message
)
class OCIInvalidOCIDError(OCIBaseException):
"""Exception raised when OCI OCID format is invalid."""
def __init__(self, file=None, original_exception=None, message=None):
super().__init__(
7012, file=file, original_exception=original_exception, message=message
)

View File

@@ -0,0 +1,123 @@
from argparse import ArgumentTypeError, Namespace
from re import match
from prowler.providers.oraclecloud.config import OCI_DEFAULT_CONFIG_FILE, OCI_REGIONS
def init_parser(self):
"""Init the OCI Provider CLI parser"""
oci_parser = self.subparsers.add_parser(
"oci", parents=[self.common_providers_parser], help="OCI Provider"
)
# Config File Authentication Options
oci_config_subparser = oci_parser.add_argument_group("Config File Authentication")
oci_config_subparser.add_argument(
"--oci-config-file",
"-cf",
nargs="?",
default=None,
help=f"OCI config file path. Defaults to {OCI_DEFAULT_CONFIG_FILE}",
)
oci_config_subparser.add_argument(
"--profile",
"-p",
nargs="?",
default=None,
help="OCI profile to use from the config file. Defaults to DEFAULT",
)
# Instance Principal Authentication
oci_instance_principal_subparser = oci_parser.add_argument_group(
"Instance Principal Authentication"
)
oci_instance_principal_subparser.add_argument(
"--use-instance-principal",
"--instance-principal",
action="store_true",
help="Use OCI Instance Principal authentication (only works when running inside an OCI compute instance)",
)
# OCI Regions
oci_regions_subparser = oci_parser.add_argument_group("OCI Regions")
oci_regions_subparser.add_argument(
"--region",
"-r",
nargs="?",
help="OCI region to run Prowler against. If not specified, all subscribed regions will be audited",
choices=list(OCI_REGIONS.keys()),
)
# OCI Compartments
oci_compartments_subparser = oci_parser.add_argument_group("OCI Compartments")
oci_compartments_subparser.add_argument(
"--compartment-id",
"--compartment",
nargs="+",
default=None,
type=validate_compartment_ocid,
help="OCI compartment OCIDs to audit. If not specified, all compartments in the tenancy will be audited",
)
def validate_compartment_ocid(ocid: str) -> str:
"""
Validates that the input compartment OCID is valid.
Args:
ocid (str): The compartment OCID to validate.
Returns:
str: The validated compartment OCID.
Raises:
ArgumentTypeError: If the compartment OCID is invalid.
"""
# OCID pattern for compartment: ocid1.compartment.<realm>.<region>.<unique_id>
# or ocid1.tenancy.<realm>.<region>.<unique_id> for root compartment
ocid_pattern = (
r"^ocid1\.(compartment|tenancy)\.[a-z0-9_-]+\.[a-z0-9_-]*\.[a-z0-9]+$"
)
if match(ocid_pattern, ocid):
return ocid
else:
raise ArgumentTypeError(
f"Invalid compartment OCID format: {ocid}. "
"Expected format: ocid1.compartment.<realm>.<region>.<unique_id>"
)
def validate_arguments(arguments: Namespace) -> tuple[bool, str]:
"""
validate_arguments returns {True, ""} if the provider arguments passed are valid
and can be used together. It performs an extra validation, specific for the OCI provider,
apart from the argparse lib.
Args:
arguments (Namespace): The parsed arguments.
Returns:
tuple[bool, str]: A tuple containing a boolean indicating validity and an error message.
"""
# Check if instance principal and config file/profile are used together
if arguments.use_instance_principal and (
arguments.oci_config_file or arguments.profile
):
return (
False,
"Cannot use --use-instance-principal with --oci-config-file or --profile options",
)
# # Validate compartment OCIDs if provided
# if arguments.compartment_id:
# for compartment_id in arguments.compartment_id:
# if not OciProvider.validate_ocid(compartment_id, "compartment"):
# # Check if it's a tenancy OCID (root compartment)
# if not OciProvider.validate_ocid(compartment_id, "tenancy"):
# return (
# False,
# f"Invalid compartment OCID: {compartment_id}",
# )
return (True, "")

View File

@@ -0,0 +1,176 @@
from prowler.lib.logger import logger
from prowler.lib.mutelist.mutelist import Mutelist
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
class OCIMutelist(Mutelist):
"""
OCIMutelist class manages the mutelist functionality for OCI provider.
This class extends the base Mutelist class to provide OCI-specific functionality
for muting findings based on tenancy, check, region, resource, and tags.
"""
def __init__(
self,
mutelist_content: dict = {},
mutelist_path: str = None,
tenancy_id: str = "",
) -> "OCIMutelist":
"""
Initialize the OCIMutelist.
Args:
mutelist_content (dict): The mutelist content as a dictionary.
mutelist_path (str): The path to the mutelist file.
tenancy_id (str): The OCI tenancy ID.
"""
self._mutelist = mutelist_content
self._mutelist_file_path = mutelist_path
self._tenancy_id = tenancy_id
if mutelist_path:
# Load mutelist from local file
self.get_mutelist_file_from_local_file(mutelist_path)
if self._mutelist:
self._mutelist = self.validate_mutelist(self._mutelist)
def is_finding_muted(
self,
finding,
tenancy_id: str,
) -> bool:
"""
Check if a finding is muted based on the mutelist.
Args:
finding: The finding object to check (should have check_metadata, region, resource_id, resource_tags).
tenancy_id (str): The OCI tenancy ID.
Returns:
bool: True if the finding is muted, False otherwise.
"""
try:
check_id = finding.check_metadata.CheckID
region = finding.region if hasattr(finding, "region") else ""
resource_id = finding.resource_id if hasattr(finding, "resource_id") else ""
resource_tags = {}
# Handle resource tags
if hasattr(finding, "resource_tags") and finding.resource_tags:
resource_tags = unroll_dict(unroll_tags(finding.resource_tags))
return self.is_muted(
tenancy_id,
check_id,
region,
resource_id,
resource_tags,
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return False
def is_muted(
self,
tenancy_id: str,
check_id: str,
region: str,
resource_id: str,
resource_tags: dict,
) -> bool:
"""
Check if a specific combination is muted.
Args:
tenancy_id (str): The OCI tenancy ID.
check_id (str): The check ID.
region (str): The OCI region.
resource_id (str): The resource ID (OCID).
resource_tags (dict): The resource tags.
Returns:
bool: True if muted, False otherwise.
"""
try:
if not self._mutelist:
return False
# Check if mutelist has Accounts/Tenancies section
tenancies = self._mutelist.get("Accounts", {})
if not tenancies:
# Try with "Tenancies" key for OCI-specific mutelist
tenancies = self._mutelist.get("Tenancies", {})
# Check for wildcard or specific tenancy
tenancy_mutelist = tenancies.get("*", {})
if tenancy_id in tenancies:
# Merge with specific tenancy rules
specific_tenancy = tenancies.get(tenancy_id, {})
tenancy_mutelist = {**tenancy_mutelist, **specific_tenancy}
if not tenancy_mutelist:
return False
# Get checks for this tenancy
checks = tenancy_mutelist.get("Checks", {})
# Check for wildcard or specific check
check_mutelist = checks.get("*", {})
if check_id in checks:
specific_check = checks.get(check_id, {})
check_mutelist = {**check_mutelist, **specific_check}
if not check_mutelist:
return False
# Check regions
regions = check_mutelist.get("Regions", [])
if regions and "*" not in regions and region not in regions:
return False
# Check resources
resources = check_mutelist.get("Resources", [])
if resources:
if "*" not in resources and resource_id not in resources:
return False
# Check tags
tags = check_mutelist.get("Tags", [])
if tags and resource_tags:
# Check if any tag matches
tag_match = False
for tag_filter in tags:
# Tag filter format: "key=value" or "key=*"
if "=" in tag_filter:
key, value = tag_filter.split("=", 1)
if key in resource_tags:
if value == "*" or resource_tags[key] == value:
tag_match = True
break
if not tag_match:
return False
# Check exceptions (resources that should NOT be muted)
exceptions = check_mutelist.get("Exceptions", {})
if exceptions:
exception_resources = exceptions.get("Resources", [])
if resource_id in exception_resources:
return False
exception_regions = exceptions.get("Regions", [])
if region in exception_regions:
return False
# If we passed all checks, the finding is muted
return True
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return False

View File

@@ -0,0 +1,213 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.oci_provider import OciProvider
MAX_WORKERS = 10
class OCIService:
"""
The OCIService class offers a parent class for each OCI Service to generate:
- OCI Regional Clients
- Shared information like the tenancy ID, user ID, and the checks audited
- OCI Session configuration
- Thread pool for the __threading_call__
- Handles compartment traversal
"""
def __init__(self, service: str, provider: OciProvider):
"""
Initialize the OCIService base class.
Args:
service (str): The OCI service name (e.g., 'compute', 'object_storage').
provider (OciProvider): The OCI provider instance.
"""
# Audit Information
self.provider = provider
self.audited_tenancy = provider.identity.tenancy_id
self.audited_user = provider.identity.user_id
self.audited_regions = provider.regions
self.audited_compartments = provider.compartments
self.audited_checks = provider.audit_metadata.expected_checks
self.audit_config = provider.audit_config
self.fixer_config = provider.fixer_config
# OCI Session
self.session_config = provider.session.config
self.session_signer = provider.session.signer
# Service name
self.service = service.lower() if not service.islower() else service
# Generate Regional Clients
self.regional_clients = provider.generate_regional_clients(self.service)
# Thread pool for __threading_call__
self.thread_pool = ThreadPoolExecutor(max_workers=MAX_WORKERS)
def __get_session_config__(self):
"""Get the OCI session configuration."""
return self.session_config
def __get_session_signer__(self):
"""Get the OCI session signer."""
return self.session_signer
def __threading_call__(self, call, iterator=None):
"""
Execute a function across multiple items using threading.
Args:
call (callable): The function to call for each item.
iterator (list, optional): A list of items to process. Defaults to regional clients.
"""
# Use the provided iterator, or default to self.regional_clients
items = (
iterator if iterator is not None else list(self.regional_clients.values())
)
# Determine the total count for logging
item_count = len(items)
# Trim leading and trailing underscores from the call's name
call_name = call.__name__.strip("_")
# Add Capitalization
call_name = " ".join([x.capitalize() for x in call_name.split("_")])
# Print a message based on the call's name, and if its regional or processing a list of items
if iterator is None:
logger.info(
f"{self.service.upper()} - Starting threads for '{call_name}' function across {item_count} regions..."
)
else:
logger.info(
f"{self.service.upper()} - Starting threads for '{call_name}' function to process {item_count} items..."
)
# Submit tasks to the thread pool
futures = [self.thread_pool.submit(call, item) for item in items]
# Wait for all tasks to complete
for future in as_completed(futures):
try:
future.result() # Raises exceptions from the thread, if any
except Exception as error:
logger.error(
f"{self.service.upper()} - Error in threaded execution: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __threading_call_by_compartment__(self, call):
"""
Execute a function for each compartment using threading.
Args:
call (callable): The function to call for each compartment.
The function should accept a compartment object.
"""
# Use compartments as the iterator
compartments = self.audited_compartments
compartment_count = len(compartments)
# Trim leading and trailing underscores from the call's name
call_name = call.__name__.strip("_")
# Add Capitalization
call_name = " ".join([x.capitalize() for x in call_name.split("_")])
logger.info(
f"{self.service.upper()} - Starting threads for '{call_name}' function across {compartment_count} compartments..."
)
# Submit tasks to the thread pool
futures = [
self.thread_pool.submit(call, compartment) for compartment in compartments
]
# Wait for all tasks to complete
for future in as_completed(futures):
try:
future.result() # Raises exceptions from the thread, if any
except Exception as error:
logger.error(
f"{self.service.upper()} - Error in compartment threaded execution: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __threading_call_by_region_and_compartment__(self, call):
"""
Execute a function for each region and compartment combination using threading.
Args:
call (callable): The function to call for each (region, compartment) pair.
The function should accept region and compartment as parameters.
"""
# Create combinations of regions and compartments
region_compartment_pairs = [
(region, compartment)
for region in self.audited_regions
for compartment in self.audited_compartments
]
pair_count = len(region_compartment_pairs)
# Trim leading and trailing underscores from the call's name
call_name = call.__name__.strip("_")
# Add Capitalization
call_name = " ".join([x.capitalize() for x in call_name.split("_")])
logger.info(
f"{self.service.upper()} - Starting threads for '{call_name}' function across {pair_count} region-compartment pairs..."
)
# Submit tasks to the thread pool
futures = [
self.thread_pool.submit(call, region, compartment)
for region, compartment in region_compartment_pairs
]
# Wait for all tasks to complete
for future in as_completed(futures):
try:
future.result() # Raises exceptions from the thread, if any
except Exception as error:
logger.error(
f"{self.service.upper()} - Error in region-compartment threaded execution: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def get_client_for_region(self, region_key: str):
"""
Get the OCI service client for a specific region.
Args:
region_key (str): The region key (e.g., 'us-ashburn-1').
Returns:
The OCI service client for the region, or None if not found.
"""
return self.regional_clients.get(region_key)
def _create_oci_client(self, client_class, config_overrides=None, **kwargs):
"""
Create an OCI SDK client with proper authentication handling.
Args:
client_class: The OCI SDK client class to instantiate
config_overrides: Optional dict to merge with session_config (e.g., {"region": "us-ashburn-1"})
**kwargs: Additional arguments to pass to the client constructor
Returns:
An instance of the OCI SDK client
This helper method handles the different authentication methods:
- API Key: signer is None, SDK uses key_file from config
- Session Token: signer is SecurityTokenSigner
- Instance Principal: signer is InstancePrincipalsSecurityTokenSigner
"""
# Merge config overrides if provided
config = {**self.session_config, **(config_overrides or {})}
# Only pass signer if it's not None
# For API key auth, signer is None and the SDK uses the key from config
if self.session_signer:
return client_class(config=config, signer=self.session_signer, **kwargs)
else:
return client_class(config=config, **kwargs)

View File

@@ -0,0 +1,96 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from prowler.config.config import output_file_timestamp
from prowler.providers.common.models import ProviderOutputOptions
@dataclass
class OCICredentials:
"""OCI Credentials model"""
user: str
fingerprint: str
key_file: Optional[str]
key_content: Optional[str]
tenancy: str
region: str
pass_phrase: Optional[str] = None
@dataclass
class OCIIdentityInfo:
"""OCI Identity Information model"""
tenancy_id: str
tenancy_name: str
user_id: str
region: str
profile: Optional[str]
audited_regions: set
audited_compartments: list
@dataclass
class OCICompartment:
"""OCI Compartment model"""
id: str
name: str
lifecycle_state: str
time_created: datetime
description: Optional[str] = None
freeform_tags: Optional[dict] = None
defined_tags: Optional[dict] = None
@dataclass
class OCISession:
"""OCI Session model to store configuration and signer"""
config: dict
signer: object
profile: Optional[str] = None
@dataclass
class OCIRegion:
"""OCI Region model"""
key: str
name: str
is_home_region: bool = False
@dataclass
class OCIRegionalClient:
"""OCI Regional Client wrapper model"""
client: object
region: str
class OCIOutputOptions(ProviderOutputOptions):
"""OCI Output Options model"""
def __init__(self, arguments, bulk_checks_metadata, identity):
# First call Provider_Output_Options init
super().__init__(arguments, bulk_checks_metadata)
# Check if custom output filename was input, if not, set the default
if (
not hasattr(arguments, "output_filename")
or arguments.output_filename is None
):
# Use tenancy name if available, otherwise fall back to tenancy ID
tenancy_identifier = (
identity.tenancy_name
if identity.tenancy_name and identity.tenancy_name != "unknown"
else identity.tenancy_id
)
self.output_filename = (
f"prowler-output-{tenancy_identifier}-{output_file_timestamp}"
)
else:
self.output_filename = arguments.output_filename

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
"""OCI Analytics client."""
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.analytics.analytics_service import Analytics
analytics_client = Analytics(Provider.get_global_provider())

View File

@@ -0,0 +1,36 @@
{
"Provider": "oci",
"CheckID": "analytics_instance_access_restricted",
"CheckTitle": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "analytics",
"SubServiceName": "",
"ResourceIdTemplate": "oci:analytics:instance",
"Severity": "high",
"ResourceType": "AnalyticsInstance",
"Description": "Oracle Analytics Cloud access should be restricted or deployed in VCN.",
"Risk": "Not meeting this network security requirement increases risk of unauthorized access.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Network/home.htm",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network",
"Url": "https://hub.prowler.com/check/oci/analytics_instance_access_restricted"
}
},
"Categories": [
"network-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,48 @@
"""Check Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.analytics.analytics_client import (
analytics_client,
)
class analytics_instance_access_restricted(Check):
"""Check Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network."""
def execute(self) -> Check_Report_OCI:
"""Execute the analytics_instance_access_restricted check."""
findings = []
for instance in analytics_client.analytics_instances:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=instance,
region=instance.region,
resource_name=instance.name,
resource_id=instance.id,
compartment_id=instance.compartment_id,
)
# Check if instance has PUBLIC network endpoint type
if (
instance.network_endpoint_type
and instance.network_endpoint_type.upper() == "PUBLIC"
):
# Check if whitelisted IPs are configured
if not instance.whitelisted_ips:
report.status = "FAIL"
report.status_extended = f"Analytics instance {instance.name} has public access with no whitelisted IPs configured."
# Check if 0.0.0.0/0 is in whitelisted IPs
elif "0.0.0.0/0" in instance.whitelisted_ips:
report.status = "FAIL"
report.status_extended = f"Analytics instance {instance.name} has public access with unrestricted IP range (0.0.0.0/0)."
else:
report.status = "PASS"
report.status_extended = f"Analytics instance {instance.name} has public access with restricted whitelisted IPs."
else:
report.status = "PASS"
report.status_extended = f"Analytics instance {instance.name} is deployed within a VCN or has restricted access."
findings.append(report)
return findings

View File

@@ -0,0 +1,99 @@
"""OCI Analytics service."""
from typing import Optional
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class Analytics(OCIService):
"""OCI Analytics service class."""
def __init__(self, provider):
"""Initialize Analytics service."""
super().__init__("analytics", provider)
self.analytics_instances = []
self.__threading_call_by_region_and_compartment__(
self.__list_analytics_instances__
)
def __get_client__(self, region: str) -> oci.analytics.AnalyticsClient:
"""Get OCI Analytics client for a region."""
return self._create_oci_client(
oci.analytics.AnalyticsClient, config_overrides={"region": region}
)
def __list_analytics_instances__(self, region, compartment):
"""List all analytics instances in a compartment."""
try:
region_key = region.key if hasattr(region, "key") else str(region)
analytics_client = self.__get_client__(region_key)
instances = oci.pagination.list_call_get_all_results(
analytics_client.list_analytics_instances, compartment_id=compartment.id
).data
for instance in instances:
# Only include ACTIVE or INACTIVE or UPDATING instances
if instance.lifecycle_state in ["ACTIVE", "INACTIVE", "UPDATING"]:
# Extract network endpoint details
network_endpoint_type = None
whitelisted_ips = []
if (
hasattr(instance, "network_endpoint_details")
and instance.network_endpoint_details
):
network_endpoint_type = getattr(
instance.network_endpoint_details,
"network_endpoint_type",
None,
)
whitelisted_ips = (
getattr(
instance.network_endpoint_details, "whitelisted_ips", []
)
or []
)
self.analytics_instances.append(
AnalyticsInstance(
id=instance.id,
name=instance.name,
compartment_id=instance.compartment_id,
region=region_key,
lifecycle_state=instance.lifecycle_state,
network_endpoint_type=network_endpoint_type,
whitelisted_ips=whitelisted_ips,
description=getattr(instance, "description", None),
email_notification=getattr(
instance, "email_notification", None
),
feature_set=getattr(instance, "feature_set", None),
service_url=getattr(instance, "service_url", None),
)
)
except Exception as error:
logger.error(
f"{region_key if 'region_key' in locals() else region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class AnalyticsInstance(BaseModel):
"""OCI Analytics Instance model."""
id: str
name: str
compartment_id: str
region: str
lifecycle_state: str
network_endpoint_type: Optional[str]
whitelisted_ips: list[str]
description: Optional[str] = None
email_notification: Optional[str] = None
feature_set: Optional[str] = None
service_url: Optional[str] = None

View File

@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.audit.audit_service import Audit
audit_client = Audit(Provider.get_global_provider())

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "audit_log_retention_period_365_days",
"CheckTitle": "Ensure audit log retention period is set to 365 days or greater",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "audit",
"SubServiceName": "",
"ResourceIdTemplate": "oci:audit:configuration",
"Severity": "medium",
"ResourceType": "OciAudit",
"Description": "Ensure audit log retention period is set to 365 days or greater",
"Risk": "Inadequate audit logging increases risk of undetected security incidents.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Audit/Tasks/settingretentionperiod.htm",
"Remediation": {
"Code": {
"CLI": "oci audit configuration update --compartment-id <tenancy-ocid> --retention-period-days 365",
"NativeIaC": "",
"Other": "1. Navigate to Governance > Audit\n2. Click Configuration\n3. Set retention period to 365 days or greater\n4. Save changes",
"Terraform": "resource \"oci_audit_configuration\" \"example\" {\n compartment_id = var.tenancy_ocid\n retention_period_days = 365\n}"
},
"Recommendation": {
"Text": "Ensure audit log retention period is set to 365 days or greater",
"Url": "https://hub.prowler.com/check/oci/audit_log_retention_period_365_days"
}
},
"Categories": [
"logging",
"security-configuration"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,46 @@
"""Check Ensure audit log retention period is set to 365 days or greater."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.audit.audit_client import audit_client
class audit_log_retention_period_365_days(Check):
"""Check Ensure audit log retention period is set to 365 days or greater."""
def execute(self) -> Check_Report_OCI:
"""Execute the audit_log_retention_period_365_days check."""
findings = []
# Check audit log retention period
if audit_client.configuration:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=audit_client.configuration,
region="global",
resource_name="Audit Configuration",
resource_id=audit_client.audited_tenancy,
compartment_id=audit_client.audited_tenancy,
)
if audit_client.configuration.retention_period_days >= 365:
report.status = "PASS"
report.status_extended = f"Audit log retention period is {audit_client.configuration.retention_period_days} days (365 days or greater)."
else:
report.status = "FAIL"
report.status_extended = f"Audit log retention period is {audit_client.configuration.retention_period_days} days (less than 365 days)."
findings.append(report)
else:
report = Check_Report_OCI(
metadata=self.metadata(),
resource={},
region="global",
resource_name="Audit Configuration",
resource_id=audit_client.audited_tenancy,
compartment_id=audit_client.audited_tenancy,
)
report.status = "FAIL"
report.status_extended = "Audit configuration not found."
findings.append(report)
return findings

View File

@@ -0,0 +1,57 @@
"""OCI Audit Service Module."""
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class Audit(OCIService):
"""OCI Audit Service class."""
def __init__(self, provider):
"""Initialize the Audit service."""
super().__init__("audit", provider)
self.configuration = None
self.__get_configuration__()
def __get_configuration__(self):
"""Get Audit configuration."""
try:
audit_client = self._create_oci_client(oci.audit.AuditClient)
logger.info("Audit - Getting Configuration...")
try:
config = audit_client.get_configuration(
compartment_id=self.audited_tenancy
).data
self.configuration = AuditConfiguration(
compartment_id=self.audited_tenancy,
retention_period_days=(
config.retention_period_days
if hasattr(config, "retention_period_days")
else 90
),
)
except Exception as error:
logger.error(
f"Audit - Error getting audit configuration: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
self.configuration = AuditConfiguration(
compartment_id=self.audited_tenancy, retention_period_days=90
)
except Exception as error:
logger.error(
f"Audit - Error in audit service initialization: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Service Models
class AuditConfiguration(BaseModel):
"""OCI Audit Configuration model."""
compartment_id: str
retention_period_days: int = 90

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "blockstorage_block_volume_encrypted_with_cmk",
"CheckTitle": "Ensure Block Volumes are encrypted with Customer Managed Keys",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "blockstorage",
"SubServiceName": "",
"ResourceIdTemplate": "oci:blockstorage:volume",
"Severity": "medium",
"ResourceType": "OciBlockVolume",
"Description": "Block volumes should be encrypted with Customer Managed Keys (CMK) for enhanced security and control over encryption keys.",
"Risk": "Using Oracle-managed encryption keys instead of Customer Managed Keys reduces control over encryption key lifecycle and access policies.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/overview.htm",
"Remediation": {
"Code": {
"CLI": "oci bv volume create --compartment-id <compartment-ocid> --availability-domain <ad> --kms-key-id <kms-key-ocid>",
"NativeIaC": "",
"Other": "1. Navigate to Block Storage > Block Volumes\n2. Create a new volume or update existing\n3. Under 'Encryption', select 'Encrypt using customer-managed keys'\n4. Select the KMS vault and key\n5. Click 'Create' or 'Save Changes'",
"Terraform": "resource \"oci_core_volume\" \"example\" {\n compartment_id = var.compartment_id\n availability_domain = var.availability_domain\n kms_key_id = var.kms_key_id\n}"
},
"Recommendation": {
"Text": "Encrypt all block volumes with Customer Managed Keys for better security control.",
"Url": "https://hub.prowler.com/check/oci/blockstorage_block_volume_encrypted_with_cmk"
}
},
"Categories": [
"encryption",
"storage"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,39 @@
"""Check if Block Volumes are encrypted with Customer Managed Keys."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.blockstorage.blockstorage_client import (
blockstorage_client,
)
class blockstorage_block_volume_encrypted_with_cmk(Check):
"""Check if Block Volumes are encrypted with Customer Managed Keys."""
def execute(self) -> Check_Report_OCI:
"""Execute the blockstorage_block_volume_encrypted_with_cmk check.
Returns:
List of Check_Report_OCI objects with findings
"""
findings = []
for volume in blockstorage_client.volumes:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=volume,
region=volume.region,
resource_name=volume.name,
resource_id=volume.id,
compartment_id=volume.compartment_id,
)
if volume.kms_key_id is not None:
report.status = "PASS"
report.status_extended = f"Block volume {volume.name} is encrypted with a Customer Managed Key (CMK)."
else:
report.status = "FAIL"
report.status_extended = f"Block volume {volume.name} is not encrypted with a Customer Managed Key (uses Oracle-managed encryption)."
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "oci",
"CheckID": "blockstorage_boot_volume_encrypted_with_cmk",
"CheckTitle": "Ensure Boot Volumes are encrypted with Customer Managed Key",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "blockstorage",
"SubServiceName": "",
"ResourceIdTemplate": "oci:blockstorage:resource",
"Severity": "medium",
"ResourceType": "OciBlockstorageResource",
"Description": "Boot volumes should be encrypted with Customer Managed Keys (CMK) for enhanced security and control over encryption keys.",
"Risk": "Not meeting this requirement increases security risk.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/oci/OCI-BlockVolume/block-volumes-encrypted-with-cmks.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure Boot Volumes are encrypted with Customer Managed Key",
"Url": "https://hub.prowler.com/check/oci/blockstorage_boot_volume_encrypted_with_cmk"
}
},
"Categories": [
"security-configuration"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,35 @@
"""Check Ensure Boot Volumes are encrypted with Customer Managed Key."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.blockstorage.blockstorage_client import (
blockstorage_client,
)
class blockstorage_boot_volume_encrypted_with_cmk(Check):
"""Check Ensure Boot Volumes are encrypted with Customer Managed Key."""
def execute(self) -> Check_Report_OCI:
"""Execute the blockstorage_boot_volume_encrypted_with_cmk check."""
findings = []
for resource in blockstorage_client.boot_volumes:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=resource,
region=resource.region,
resource_name=resource.name,
resource_id=resource.id,
compartment_id=resource.compartment_id,
)
if resource.kms_key_id is not None:
report.status = "PASS"
report.status_extended = f"Boot Volume {resource.name} is encrypted with Customer Managed Key."
else:
report.status = "FAIL"
report.status_extended = f"Boot Volume {resource.name} is not encrypted with Customer Managed Key."
findings.append(report)
return findings

View File

@@ -0,0 +1,6 @@
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.blockstorage.blockstorage_service import (
BlockStorage,
)
blockstorage_client = BlockStorage(Provider.get_global_provider())

View File

@@ -0,0 +1,182 @@
"""OCI Block Storage Service Module."""
from typing import Optional
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class BlockStorage(OCIService):
"""OCI Block Storage Service class to retrieve block volumes and boot volumes."""
def __init__(self, provider):
"""
Initialize the Block Storage service.
Args:
provider: The OCI provider instance
"""
super().__init__("blockstorage", provider)
self.volumes = []
self.boot_volumes = []
self.__threading_call__(self.__list_volumes__)
self.__threading_call__(self.__list_boot_volumes__)
def __get_client__(self, region):
"""
Get the Block Storage client for a region.
Args:
region: Region key
Returns:
Block Storage client instance
"""
client_region = self.regional_clients.get(region)
if client_region:
return self._create_oci_client(oci.core.BlockstorageClient)
return None
def __list_volumes__(self, regional_client):
"""
List all block volumes in the compartments.
Args:
regional_client: Regional OCI client
"""
try:
blockstorage_client = self.__get_client__(regional_client.region)
if not blockstorage_client:
return
logger.info(
f"BlockStorage - Listing Volumes in {regional_client.region}..."
)
for compartment in self.audited_compartments:
try:
volumes = oci.pagination.list_call_get_all_results(
blockstorage_client.list_volumes, compartment_id=compartment.id
).data
for volume in volumes:
if volume.lifecycle_state not in ["TERMINATED", "TERMINATING"]:
self.volumes.append(
Volume(
id=volume.id,
name=(
volume.display_name
if hasattr(volume, "display_name")
else volume.id
),
compartment_id=compartment.id,
region=regional_client.region,
lifecycle_state=volume.lifecycle_state,
kms_key_id=(
volume.kms_key_id
if hasattr(volume, "kms_key_id")
else None
),
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __list_boot_volumes__(self, regional_client):
"""
List all boot volumes in the compartments.
Args:
regional_client: Regional OCI client
"""
try:
blockstorage_client = self.__get_client__(regional_client.region)
if not blockstorage_client:
return
logger.info(
f"BlockStorage - Listing Boot Volumes in {regional_client.region}..."
)
for compartment in self.audited_compartments:
try:
# Get availability domains for this compartment
identity_client = self._create_oci_client(
oci.identity.IdentityClient
)
availability_domains = identity_client.list_availability_domains(
compartment_id=compartment.id
).data
for ad in availability_domains:
boot_volumes = oci.pagination.list_call_get_all_results(
blockstorage_client.list_boot_volumes,
availability_domain=ad.name,
compartment_id=compartment.id,
).data
for boot_volume in boot_volumes:
if boot_volume.lifecycle_state not in [
"TERMINATED",
"TERMINATING",
]:
self.boot_volumes.append(
BootVolume(
id=boot_volume.id,
name=(
boot_volume.display_name
if hasattr(boot_volume, "display_name")
else boot_volume.id
),
compartment_id=compartment.id,
region=regional_client.region,
lifecycle_state=boot_volume.lifecycle_state,
kms_key_id=(
boot_volume.kms_key_id
if hasattr(boot_volume, "kms_key_id")
else None
),
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Service Models
class Volume(BaseModel):
"""OCI Block Volume model."""
id: str
name: str
compartment_id: str
region: str
lifecycle_state: str
kms_key_id: Optional[str] = None
class BootVolume(BaseModel):
"""OCI Boot Volume model."""
id: str
name: str
compartment_id: str
region: str
lifecycle_state: str
kms_key_id: Optional[str] = None

View File

@@ -0,0 +1,6 @@
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.cloudguard.cloudguard_service import (
CloudGuard,
)
cloudguard_client = CloudGuard(Provider.get_global_provider())

View File

@@ -0,0 +1,36 @@
{
"Provider": "oci",
"CheckID": "cloudguard_enabled",
"CheckTitle": "Ensure Cloud Guard is enabled in the root compartment of the tenancy",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "cloudguard",
"SubServiceName": "",
"ResourceIdTemplate": "oci:cloudguard:configuration",
"Severity": "high",
"ResourceType": "OciCloudGuard",
"Description": "Ensure Cloud Guard is enabled in the root compartment of the tenancy",
"Risk": "Without Cloud Guard, security threats may not be detected and remediated.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/cloud-guard/home.htm",
"Remediation": {
"Code": {
"CLI": "oci cloud-guard configuration update --compartment-id <tenancy-ocid> --status ENABLED --reporting-region <region>",
"NativeIaC": "",
"Other": "1. Navigate to Security > Cloud Guard\n2. Enable Cloud Guard\n3. Select reporting region\n4. Configure detectors and responders",
"Terraform": "resource \"oci_cloud_guard_cloud_guard_configuration\" \"example\" {\n compartment_id = var.tenancy_ocid\n reporting_region = var.region\n status = \"ENABLED\"\n}"
},
"Recommendation": {
"Text": "Ensure Cloud Guard is enabled in the root compartment of the tenancy",
"Url": "https://hub.prowler.com/check/oci/cloudguard_enabled"
}
},
"Categories": [
"monitoring"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,39 @@
"""Check Ensure Cloud Guard is enabled in the root compartment of the tenancy."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.cloudguard.cloudguard_client import (
cloudguard_client,
)
class cloudguard_enabled(Check):
"""Check Ensure Cloud Guard is enabled in the root compartment of the tenancy."""
def execute(self) -> Check_Report_OCI:
"""Execute the cloudguard_enabled check."""
findings = []
report = Check_Report_OCI(
metadata=self.metadata(),
resource=cloudguard_client.configuration,
region="global",
resource_name="Cloud Guard",
resource_id=cloudguard_client.audited_tenancy,
compartment_id=cloudguard_client.audited_tenancy,
)
if (
cloudguard_client.configuration
and cloudguard_client.configuration.status == "ENABLED"
):
report.status = "PASS"
report.status_extended = "Cloud Guard is enabled in the root compartment."
else:
report.status = "FAIL"
report.status_extended = (
"Cloud Guard is not enabled in the root compartment."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,63 @@
"""OCI Cloud Guard Service Module."""
from typing import Optional
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class CloudGuard(OCIService):
"""OCI Cloud Guard Service class."""
def __init__(self, provider):
"""Initialize the Cloud Guard service."""
super().__init__("cloudguard", provider)
self.configuration = None
self.__get_configuration__()
def __get_configuration__(self):
"""Get Cloud Guard configuration."""
try:
cloudguard_client = self._create_oci_client(
oci.cloud_guard.CloudGuardClient
)
logger.info("CloudGuard - Getting Configuration...")
try:
config = cloudguard_client.get_configuration(
compartment_id=self.audited_tenancy
).data
self.configuration = CloudGuardConfiguration(
compartment_id=self.audited_tenancy,
status=config.status if hasattr(config, "status") else "DISABLED",
reporting_region=(
config.reporting_region
if hasattr(config, "reporting_region")
else None
),
)
except Exception as error:
logger.info(f"CloudGuard - Cloud Guard not configured: {error}")
self.configuration = CloudGuardConfiguration(
compartment_id=self.audited_tenancy,
status="DISABLED",
reporting_region=None,
)
except Exception as error:
logger.error(
f"CloudGuard - Error getting Cloud Guard configuration: {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Service Models
class CloudGuardConfiguration(BaseModel):
"""OCI Cloud Guard Configuration model."""
compartment_id: str
status: str
reporting_region: Optional[str] = None

View File

@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.compute.compute_service import Compute
compute_client = Compute(Provider.get_global_provider())

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "compute_instance_in_transit_encryption_enabled",
"CheckTitle": "Ensure In-transit Encryption is enabled on Compute Instance",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "compute",
"SubServiceName": "",
"ResourceIdTemplate": "oci:compute:instance",
"Severity": "high",
"ResourceType": "OciComputeInstance",
"Description": "In-transit encryption protects data as it moves between the compute instance and block volumes. This is implemented through the Oracle Cloud Agent management plugin which enables encryption for block volume attachments.",
"Risk": "Without in-transit encryption, data moving between compute instances and block volumes could be intercepted or tampered with during transmission.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Block/Concepts/blockvolumeencryption.htm",
"Remediation": {
"Code": {
"CLI": "oci compute instance update --instance-id <instance-ocid> --agent-config '{\"isManagementDisabled\": false}'",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/oci/OCI-Compute/enable-encryption-in-transit.html",
"Terraform": "resource \"oci_core_instance\" \"example\" {\n # ... other configuration ...\n agent_config {\n is_management_disabled = false\n }\n}"
},
"Recommendation": {
"Text": "Enable the Oracle Cloud Agent management plugin on all compute instances to enable in-transit encryption for block volume attachments.",
"Url": "https://hub.prowler.com/check/oci/compute_instance_in_transit_encryption_enabled"
}
},
"Categories": [
"compute",
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,38 @@
"""Check if In-transit Encryption is enabled on Compute Instance."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.compute.compute_client import compute_client
class compute_instance_in_transit_encryption_enabled(Check):
"""Check if In-transit Encryption is enabled on Compute Instance."""
def execute(self) -> Check_Report_OCI:
"""Execute the compute_instance_in_transit_encryption_enabled check.
Returns:
List of Check_Report_OCI objects with findings
"""
findings = []
for instance in compute_client.instances:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=instance,
region=instance.region,
resource_name=instance.name,
resource_id=instance.id,
compartment_id=instance.compartment_id,
)
# In-transit encryption is enabled when is_pv_encryption_in_transit_enabled is True
if instance.is_pv_encryption_in_transit_enabled:
report.status = "PASS"
report.status_extended = f"Compute instance {instance.name} has in-transit encryption enabled."
else:
report.status = "FAIL"
report.status_extended = f"Compute instance {instance.name} does not have in-transit encryption enabled."
findings.append(report)
return findings

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "compute_instance_legacy_metadata_endpoint_disabled",
"CheckTitle": "Ensure Compute Instance Legacy Metadata service endpoint is disabled",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "compute",
"SubServiceName": "",
"ResourceIdTemplate": "oci:compute:instance",
"Severity": "medium",
"ResourceType": "OciComputeInstance",
"Description": "The legacy Instance Metadata Service (IMDS) v1 endpoints do not use session authentication. Disabling the legacy endpoints helps prevent unauthorized access to instance metadata.",
"Risk": "If legacy metadata endpoints are enabled, attackers who gain access to the instance may be able to access instance metadata without authentication, potentially exposing sensitive information.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm",
"Remediation": {
"Code": {
"CLI": "oci compute instance update --instance-id <instance-ocid> --instance-options '{\"areLegacyImdsEndpointsDisabled\": true}'",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/oci/OCI-Compute/enforce-imds-v2.html",
"Terraform": "resource \"oci_core_instance\" \"example\" {\n # ... other configuration ...\n instance_options {\n are_legacy_imds_endpoints_disabled = true\n }\n}"
},
"Recommendation": {
"Text": "Disable legacy metadata service endpoints on all compute instances to enforce session-based authentication.",
"Url": "https://hub.prowler.com/check/oci/compute_instance_legacy_metadata_endpoint_disabled"
}
},
"Categories": [
"compute",
"security-configuration"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,37 @@
"""Check if Compute Instance Legacy Metadata service endpoint is disabled."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.compute.compute_client import compute_client
class compute_instance_legacy_metadata_endpoint_disabled(Check):
"""Check if Compute Instance Legacy Metadata service endpoint is disabled."""
def execute(self) -> Check_Report_OCI:
"""Execute the compute_instance_legacy_metadata_endpoint_disabled check.
Returns:
List of Check_Report_OCI objects with findings
"""
findings = []
for instance in compute_client.instances:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=instance,
region=instance.region,
resource_name=instance.name,
resource_id=instance.id,
compartment_id=instance.compartment_id,
)
if instance.are_legacy_imds_endpoints_disabled:
report.status = "PASS"
report.status_extended = f"Compute instance {instance.name} has legacy metadata service endpoint disabled."
else:
report.status = "FAIL"
report.status_extended = f"Compute instance {instance.name} has legacy metadata service endpoint enabled or not configured."
findings.append(report)
return findings

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "compute_instance_secure_boot_enabled",
"CheckTitle": "Ensure Secure Boot is enabled on Compute Instance",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "compute",
"SubServiceName": "",
"ResourceIdTemplate": "oci:compute:instance",
"Severity": "medium",
"ResourceType": "OciComputeInstance",
"Description": "Secure Boot helps ensure that the instance boots using only software that is trusted by the platform firmware. This prevents rootkits and bootkits from loading during the boot process.",
"Risk": "Without Secure Boot enabled, instances are vulnerable to boot-level malware that can compromise the entire system before the operating system loads.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Compute/References/shielded-instances.htm",
"Remediation": {
"Code": {
"CLI": "oci compute instance update --instance-id <instance-ocid> --platform-config '{\"isSecureBootEnabled\": true}'",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/oci/OCI-Compute/enable-secure-boot.html",
"Terraform": "resource \"oci_core_instance\" \"example\" {\n # ... other configuration ...\n platform_config {\n type = \"AMD_MILAN_BM\" # or appropriate platform\n is_secure_boot_enabled = true\n }\n}"
},
"Recommendation": {
"Text": "Enable Secure Boot on all compute instances to protect against boot-level malware and ensure system integrity.",
"Url": "https://hub.prowler.com/check/oci/compute_instance_secure_boot_enabled"
}
},
"Categories": [
"compute",
"security-configuration"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,39 @@
"""Check if Secure Boot is enabled on Compute Instance."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.compute.compute_client import compute_client
class compute_instance_secure_boot_enabled(Check):
"""Check if Secure Boot is enabled on Compute Instance."""
def execute(self) -> Check_Report_OCI:
"""Execute the compute_instance_secure_boot_enabled check.
Returns:
List of Check_Report_OCI objects with findings
"""
findings = []
for instance in compute_client.instances:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=instance,
region=instance.region,
resource_name=instance.name,
resource_id=instance.id,
compartment_id=instance.compartment_id,
)
if instance.is_secure_boot_enabled:
report.status = "PASS"
report.status_extended = (
f"Compute instance {instance.name} has Secure Boot enabled."
)
else:
report.status = "FAIL"
report.status_extended = f"Compute instance {instance.name} does not have Secure Boot enabled."
findings.append(report)
return findings

View File

@@ -0,0 +1,136 @@
"""OCI Compute Service Module."""
from typing import Optional
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class Compute(OCIService):
"""OCI Compute Service class to retrieve compute instances."""
def __init__(self, provider):
"""
Initialize the Compute service.
Args:
provider: The OCI provider instance
"""
super().__init__("compute", provider)
self.instances = []
self.__threading_call__(self.__list_instances__)
def __get_client__(self, region):
"""
Get the Compute client for a region.
Args:
region: Region key
Returns:
Compute client instance
"""
client_region = self.regional_clients.get(region)
if client_region:
return self._create_oci_client(oci.core.ComputeClient)
return None
def __list_instances__(self, regional_client):
"""
List all compute instances in the compartments.
Args:
regional_client: Regional OCI client
"""
try:
compute_client = self.__get_client__(regional_client.region)
if not compute_client:
return
logger.info(f"Compute - Listing Instances in {regional_client.region}...")
for compartment in self.audited_compartments:
try:
instances = oci.pagination.list_call_get_all_results(
compute_client.list_instances, compartment_id=compartment.id
).data
for instance in instances:
if instance.lifecycle_state not in [
"TERMINATED",
"TERMINATING",
]:
# Get instance metadata options
metadata_options = (
instance.instance_options.are_legacy_imds_endpoints_disabled
if hasattr(instance, "instance_options")
and hasattr(
instance.instance_options,
"are_legacy_imds_endpoints_disabled",
)
else None
)
# Get secure boot status
is_secure_boot_enabled = (
instance.platform_config.is_secure_boot_enabled
if hasattr(instance, "platform_config")
and hasattr(
instance.platform_config, "is_secure_boot_enabled"
)
else False
)
# Get in-transit encryption status from launch options
is_pv_encryption_in_transit_enabled = (
instance.launch_options.is_pv_encryption_in_transit_enabled
if hasattr(instance, "launch_options")
and hasattr(
instance.launch_options,
"is_pv_encryption_in_transit_enabled",
)
else None
)
self.instances.append(
Instance(
id=instance.id,
name=(
instance.display_name
if hasattr(instance, "display_name")
else instance.id
),
compartment_id=compartment.id,
region=regional_client.region,
lifecycle_state=instance.lifecycle_state,
are_legacy_imds_endpoints_disabled=metadata_options,
is_secure_boot_enabled=is_secure_boot_enabled,
is_pv_encryption_in_transit_enabled=is_pv_encryption_in_transit_enabled,
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
continue
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Service Models
class Instance(BaseModel):
"""OCI Compute Instance model."""
id: str
name: str
compartment_id: str
region: str
lifecycle_state: str
are_legacy_imds_endpoints_disabled: Optional[bool] = None
is_secure_boot_enabled: bool = False
is_pv_encryption_in_transit_enabled: Optional[bool] = None

View File

@@ -0,0 +1,36 @@
{
"Provider": "oci",
"CheckID": "database_autonomous_database_access_restricted",
"CheckTitle": "Ensure Oracle Autonomous Shared Database (ADB) access is restricted or deployed within a VCN",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "database",
"SubServiceName": "",
"ResourceIdTemplate": "oci:database:autonomousdatabase",
"Severity": "high",
"ResourceType": "AutonomousDatabase",
"Description": "Autonomous Shared Database instances should either have IP whitelisting configured or be deployed within a VCN to restrict network access and improve security posture.",
"Risk": "Public or unrestricted Autonomous Database access increases the attack surface and risk of unauthorized access.",
"RelatedUrl": "https://docs.oracle.com/en/cloud/paas/autonomous-database/adbsa/autonomous-private-endpoints.html",
"Remediation": {
"Code": {
"CLI": "oci db autonomous-database create-private-endpoint --autonomous-database-id <adb-ocid> --subnet-id <subnet-ocid>",
"NativeIaC": "",
"Other": "1. Navigate to Autonomous Database\n2. Select the database instance\n3. Click 'More Actions' → 'Update'\n4. Under Network Access, select 'Private endpoint access only'\n5. Configure VCN and subnet for private endpoint\n6. Alternatively, configure Access Control List (ACL) with specific IP addresses",
"Terraform": "resource \"oci_database_autonomous_database\" \"adb\" {\n compartment_id = var.compartment_id\n db_name = \"MyADB\"\n display_name = \"My Autonomous Database\"\n is_free_tier = false\n db_workload = \"OLTP\"\n whitelisted_ips = [\"10.0.0.0/24\"]\n nsg_ids = [oci_core_network_security_group.adb_nsg.id]\n subnet_id = oci_core_subnet.private_subnet.id\n}"
},
"Recommendation": {
"Text": "Deploy Autonomous Databases within a VCN using private endpoints or configure strict IP whitelisting to restrict access.",
"Url": "https://hub.prowler.com/check/oci/database_autonomous_database_access_restricted"
}
},
"Categories": [
"network-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,40 @@
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.database.database_client import (
database_client,
)
class database_autonomous_database_access_restricted(Check):
"""Ensure Oracle Autonomous Shared Database (ADB) access is restricted or deployed within a VCN (CIS 2.8)"""
def execute(self):
findings = []
for adb in database_client.autonomous_databases:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=adb,
region=adb.region,
compartment_id=adb.compartment_id,
resource_id=adb.id,
resource_name=adb.display_name,
)
# Check if database has no whitelisted IPs and no subnet (unrestricted public access)
if not adb.whitelisted_ips and not adb.subnet_id:
report.status = "FAIL"
report.status_extended = f"Autonomous Database {adb.display_name} has unrestricted public access with no whitelisted IPs and is not deployed in a VCN."
# Check if database has whitelisted IPs containing 0.0.0.0/0
elif adb.whitelisted_ips and "0.0.0.0/0" in adb.whitelisted_ips:
report.status = "FAIL"
report.status_extended = f"Autonomous Database {adb.display_name} has unrestricted public access with IP range 0.0.0.0/0 in whitelisted IPs."
else:
report.status = "PASS"
if adb.subnet_id:
report.status_extended = f"Autonomous Database {adb.display_name} is deployed within a VCN."
else:
report.status_extended = f"Autonomous Database {adb.display_name} has restricted public access with whitelisted IPs."
findings.append(report)
return findings

View File

@@ -0,0 +1,6 @@
"""OCI Database client."""
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.database.database_service import Database
database_client = Database(Provider.get_global_provider())

View File

@@ -0,0 +1,79 @@
"""OCI Database service."""
from typing import Optional
import oci
from pydantic import BaseModel
from prowler.lib.logger import logger
from prowler.providers.oraclecloud.lib.service.service import OCIService
class Database(OCIService):
"""OCI Database service class."""
def __init__(self, provider):
"""Initialize Database service."""
super().__init__("database", provider)
self.autonomous_databases = []
self.__threading_call_by_region_and_compartment__(
self.__list_autonomous_databases__
)
def __get_client__(self, region: str) -> oci.database.DatabaseClient:
"""Get OCI Database client for a region."""
return self._create_oci_client(
oci.database.DatabaseClient, config_overrides={"region": region}
)
def __list_autonomous_databases__(self, region, compartment):
"""List all autonomous databases in a compartment."""
try:
region_key = region.key if hasattr(region, "key") else str(region)
database_client = self.__get_client__(region_key)
autonomous_dbs = oci.pagination.list_call_get_all_results(
database_client.list_autonomous_databases, compartment_id=compartment.id
).data
for adb in autonomous_dbs:
# Only include databases not in TERMINATED, TERMINATING, or UNAVAILABLE states
if adb.lifecycle_state not in [
oci.database.models.AutonomousDatabaseSummary.LIFECYCLE_STATE_TERMINATED,
oci.database.models.AutonomousDatabaseSummary.LIFECYCLE_STATE_TERMINATING,
oci.database.models.AutonomousDatabaseSummary.LIFECYCLE_STATE_UNAVAILABLE,
]:
self.autonomous_databases.append(
AutonomousDatabase(
id=adb.id,
display_name=adb.display_name,
compartment_id=adb.compartment_id,
region=region_key,
lifecycle_state=adb.lifecycle_state,
whitelisted_ips=(
adb.whitelisted_ips if adb.whitelisted_ips else []
),
subnet_id=adb.subnet_id,
db_name=getattr(adb, "db_name", None),
db_workload=getattr(adb, "db_workload", None),
)
)
except Exception as error:
logger.error(
f"{region_key if 'region_key' in locals() else region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class AutonomousDatabase(BaseModel):
"""OCI Autonomous Database model."""
id: str
display_name: str
compartment_id: str
region: str
lifecycle_state: str
whitelisted_ips: list[str]
subnet_id: Optional[str]
db_name: Optional[str] = None
db_workload: Optional[str] = None

View File

@@ -0,0 +1,4 @@
from prowler.providers.common.provider import Provider
from prowler.providers.oraclecloud.services.events.events_service import Events
events_client = Events(Provider.get_global_provider())

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "events_notification_topic_and_subscription_exists",
"CheckTitle": "Create at least one notification topic and subscription to receive monitoring alerts",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "events",
"SubServiceName": "",
"ResourceIdTemplate": "oci:events:rule",
"Severity": "medium",
"ResourceType": "OciEventsRule",
"Description": "At least one notification topic and subscription should exist to receive monitoring alerts.",
"Risk": "Without proper event monitoring, security-relevant changes may go unnoticed.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Events/home.htm",
"Remediation": {
"Code": {
"CLI": "oci events rule create --display-name <name> --condition <event-condition> --actions <notification-actions>",
"NativeIaC": "",
"Other": "1. Navigate to Observability & Management > Events Service\n2. Create a new rule\n3. Configure the event condition\n4. Add notification action\n5. Save the rule",
"Terraform": "resource \"oci_events_rule\" \"example\" {\n display_name = \"rule\"\n is_enabled = true\n condition = jsonencode({\n eventType = [\"com.oraclecloud.*\"]\n })\n actions {\n actions {\n action_type = \"ONS\"\n topic_id = var.topic_id\n }\n }\n}"
},
"Recommendation": {
"Text": "Create at least one notification topic and subscription to receive monitoring alerts",
"Url": "https://hub.prowler.com/check/oci/events_notification_topic_and_subscription_exists"
}
},
"Categories": [
"logging",
"monitoring"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,53 @@
"""Check Create at least one notification topic and subscription to receive monitoring alerts."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.events.events_client import events_client
class events_notification_topic_and_subscription_exists(Check):
"""Check Create at least one notification topic and subscription to receive monitoring alerts."""
def execute(self) -> Check_Report_OCI:
"""Execute the events_notification_topic_and_subscription_exists check."""
findings = []
# Check if at least one topic with subscriptions exists
has_topic_with_subscription = any(
len(topic.subscriptions) > 0 for topic in events_client.topics
)
# Create a single report for tenancy-level check
# Use the first topic's region if available, otherwise use the first audited region
region = "global"
if events_client.topics:
region = events_client.topics[0].region
elif events_client.audited_regions:
# audited_regions contains OCIRegion objects, extract the key
first_region = events_client.audited_regions[0]
region = (
first_region.key if hasattr(first_region, "key") else str(first_region)
)
report = Check_Report_OCI(
metadata=self.metadata(),
resource={},
region=region,
resource_name="Notification Service",
resource_id=events_client.audited_tenancy,
compartment_id=events_client.audited_tenancy,
)
if has_topic_with_subscription:
report.status = "PASS"
report.status_extended = (
"At least one notification topic with active subscriptions exists."
)
else:
report.status = "FAIL"
report.status_extended = (
"No notification topics with active subscriptions found."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "oci",
"CheckID": "events_rule_cloudguard_problems",
"CheckTitle": "Ensure a notification is configured for Oracle Cloud Guard problems detected",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "events",
"SubServiceName": "",
"ResourceIdTemplate": "oci:events:rule",
"Severity": "medium",
"ResourceType": "OciEventRule",
"Description": "Ensure a notification is configured for Oracle Cloud Guard problems detected",
"Risk": "Without Cloud Guard, security threats may not be detected and remediated.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/cloud-guard/home.htm",
"Remediation": {
"Code": {
"CLI": "oci cloud-guard configuration update --compartment-id <tenancy-ocid> --status ENABLED --reporting-region <region>",
"NativeIaC": "",
"Other": "1. Navigate to Security > Cloud Guard\n2. Enable Cloud Guard\n3. Select reporting region\n4. Configure detectors and responders",
"Terraform": "resource \"oci_cloud_guard_cloud_guard_configuration\" \"example\" {\n compartment_id = var.tenancy_ocid\n reporting_region = var.region\n status = \"ENABLED\"\n}"
},
"Recommendation": {
"Text": "Ensure a notification is configured for Oracle Cloud Guard problems detected",
"Url": "https://hub.prowler.com/check/oci/cloudguard_notification_configured"
}
},
"Categories": [
"monitoring"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,90 @@
"""Check Ensure a notification is configured for Oracle Cloud Guard problems detected."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.cloudguard.cloudguard_client import (
cloudguard_client,
)
from prowler.providers.oraclecloud.services.events.events_client import events_client
from prowler.providers.oraclecloud.services.events.lib.helpers import (
check_event_rule_has_notification_actions,
filter_rules_by_event_types,
)
class events_rule_cloudguard_problems(Check):
"""Check Ensure a notification is configured for Oracle Cloud Guard problems detected."""
def execute(self) -> Check_Report_OCI:
"""Execute the events_rule_cloudguard_problems check."""
findings = []
# Required event types for Cloud Guard notifications
required_event_types = [
"com.oraclecloud.cloudguard.problemdetected",
"com.oraclecloud.cloudguard.problemdismissed",
"com.oraclecloud.cloudguard.problemremediated",
]
# Get Cloud Guard reporting region (if Cloud Guard is configured)
reporting_region = None
if cloudguard_client.configuration:
reporting_region = cloudguard_client.configuration.reporting_region
# Filter rules that monitor Cloud Guard problems
matching_rules = filter_rules_by_event_types(
events_client.rules, required_event_types
)
# If reporting region is set, filter rules to only those in that region
if reporting_region:
matching_rules = [
(rule, condition)
for rule, condition in matching_rules
if rule.region == reporting_region
]
# Create findings for each matching rule
for rule, _ in matching_rules:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=rule,
region=rule.region,
resource_name=rule.name,
resource_id=rule.id,
compartment_id=rule.compartment_id,
)
# Check if the rule has notification actions
if check_event_rule_has_notification_actions(rule):
report.status = "PASS"
report.status_extended = f"Event rule '{rule.name}' is configured to monitor Cloud Guard problems with notifications."
if reporting_region:
report.status_extended += (
f" (Cloud Guard reporting region: {reporting_region})"
)
else:
report.status = "FAIL"
report.status_extended = f"Event rule '{rule.name}' monitors Cloud Guard problems but does not have notification actions configured."
findings.append(report)
# If no matching rules found, create a single FAIL finding
if not findings:
report = Check_Report_OCI(
metadata=self.metadata(),
resource={},
region=reporting_region if reporting_region else "global",
resource_name="Cloud Guard Problem Notifications",
resource_id=events_client.audited_tenancy,
compartment_id=events_client.audited_tenancy,
)
report.status = "FAIL"
region_note = (
f" in Cloud Guard reporting region '{reporting_region}'"
if reporting_region
else ""
)
report.status_extended = f"No event rules configured{region_note} to monitor Cloud Guard problems (detected, dismissed, remediated)."
findings.append(report)
return findings

View File

@@ -0,0 +1,37 @@
{
"Provider": "oci",
"CheckID": "events_rule_iam_group_changes",
"CheckTitle": "Ensure a notification is configured for IAM group changes",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS OCI Foundations Benchmark"
],
"ServiceName": "events",
"SubServiceName": "",
"ResourceIdTemplate": "oci:events:rule",
"Severity": "medium",
"ResourceType": "OciEventsRule",
"Description": "Event rules should be configured to notify on IAM group changes.",
"Risk": "Without proper event monitoring, security-relevant changes may go unnoticed.",
"RelatedUrl": "https://docs.oracle.com/en-us/iaas/Content/Events/home.htm",
"Remediation": {
"Code": {
"CLI": "oci events rule create --display-name <name> --condition <event-condition> --actions <notification-actions>",
"NativeIaC": "",
"Other": "1. Navigate to Observability & Management > Events Service\n2. Create a new rule\n3. Configure the event condition\n4. Add notification action\n5. Save the rule",
"Terraform": "resource \"oci_events_rule\" \"example\" {\n display_name = \"rule\"\n is_enabled = true\n condition = jsonencode({\n eventType = [\"com.oraclecloud.*\"]\n })\n actions {\n actions {\n action_type = \"ONS\"\n topic_id = var.topic_id\n }\n }\n}"
},
"Recommendation": {
"Text": "Ensure a notification is configured for IAM group changes",
"Url": "https://hub.prowler.com/check/oci/events_rule_iam_group_changes"
}
},
"Categories": [
"logging",
"monitoring"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,67 @@
"""Check Ensure a notification is configured for IAM group changes."""
from prowler.lib.check.models import Check, Check_Report_OCI
from prowler.providers.oraclecloud.services.events.events_client import events_client
from prowler.providers.oraclecloud.services.events.lib.helpers import (
check_event_rule_has_notification_actions,
filter_rules_by_event_types,
)
class events_rule_iam_group_changes(Check):
"""Check Ensure a notification is configured for IAM group changes."""
def execute(self) -> Check_Report_OCI:
"""Execute the events_rule_iam_group_changes check."""
findings = []
# Required event types for IAM group changes
required_event_types = [
"com.oraclecloud.identitycontrolplane.creategroup",
"com.oraclecloud.identitycontrolplane.deletegroup",
"com.oraclecloud.identitycontrolplane.updategroup",
]
# Filter rules that monitor IAM group changes
matching_rules = filter_rules_by_event_types(
events_client.rules, required_event_types
)
# Create findings for each matching rule
for rule, _ in matching_rules:
report = Check_Report_OCI(
metadata=self.metadata(),
resource=rule,
region=rule.region,
resource_name=rule.name,
resource_id=rule.id,
compartment_id=rule.compartment_id,
)
# Check if the rule has notification actions
if check_event_rule_has_notification_actions(rule):
report.status = "PASS"
report.status_extended = f"Event rule '{rule.name}' is configured to monitor IAM group changes with notifications."
else:
report.status = "FAIL"
report.status_extended = f"Event rule '{rule.name}' monitors IAM group changes but does not have notification actions configured."
findings.append(report)
# If no matching rules found, create a single FAIL finding
if not findings:
report = Check_Report_OCI(
metadata=self.metadata(),
resource={},
region="global",
resource_name="IAM Group Changes Event Rule",
resource_id=events_client.audited_tenancy,
compartment_id=events_client.audited_tenancy,
)
report.status = "FAIL"
report.status_extended = (
"No event rules configured to monitor IAM group changes."
)
findings.append(report)
return findings

Some files were not shown because too many files have changed in this diff Show More