mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
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:
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -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/*"
|
||||
|
||||
17
.github/workflows/sdk-pull-request.yml
vendored
17
.github/workflows/sdk-pull-request.yml
vendored
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
41
dashboard/compliance/cis_3_0_oci.py
Normal file
41
dashboard/compliance/cis_3_0_oci.py
Normal 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"
|
||||
)
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
474
docs/user-guide/providers/oci/authentication.mdx
Normal file
474
docs/user-guide/providers/oci/authentication.mdx
Normal 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
|
||||
|
||||

|
||||
|
||||
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)
|
||||
376
docs/user-guide/providers/oci/getting-started-oci.mdx
Normal file
376
docs/user-guide/providers/oci/getting-started-oci.mdx
Normal 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
|
||||
|
||||

|
||||
|
||||
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)
|
||||
BIN
docs/user-guide/providers/oci/images/oci-user-ocid.png
Normal file
BIN
docs/user-guide/providers/oci/images/oci-user-ocid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
63
poetry.lock
generated
63
poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
0
prowler/compliance/oci/__init__.py
Normal file
0
prowler/compliance/oci/__init__.py
Normal file
1141
prowler/compliance/oci/cis_3.0_oci.json
Normal file
1141
prowler/compliance/oci/cis_3.0_oci.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@ class Provider(str, Enum):
|
||||
IAC = "iac"
|
||||
NHN = "nhn"
|
||||
MONGODBATLAS = "mongodbatlas"
|
||||
OCI = "oci"
|
||||
|
||||
|
||||
# Compliance
|
||||
|
||||
61
prowler/config/oraclecloud_mutelist_example.yaml
Normal file
61
prowler/config/oraclecloud_mutelist_example.yaml
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
106
prowler/lib/outputs/compliance/cis/cis_oci.py
Normal file
106
prowler/lib/outputs/compliance/cis/cis_oci.py
Normal 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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
0
prowler/providers/oraclecloud/__init__.py
Normal file
0
prowler/providers/oraclecloud/__init__.py
Normal file
61
prowler/providers/oraclecloud/config.py
Normal file
61
prowler/providers/oraclecloud/config.py
Normal 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}
|
||||
197
prowler/providers/oraclecloud/exceptions/exceptions.py
Normal file
197
prowler/providers/oraclecloud/exceptions/exceptions.py
Normal 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
|
||||
)
|
||||
0
prowler/providers/oraclecloud/lib/__init__.py
Normal file
0
prowler/providers/oraclecloud/lib/__init__.py
Normal file
123
prowler/providers/oraclecloud/lib/arguments/arguments.py
Normal file
123
prowler/providers/oraclecloud/lib/arguments/arguments.py
Normal 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, "")
|
||||
176
prowler/providers/oraclecloud/lib/mutelist/mutelist.py
Normal file
176
prowler/providers/oraclecloud/lib/mutelist/mutelist.py
Normal 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
|
||||
213
prowler/providers/oraclecloud/lib/service/service.py
Normal file
213
prowler/providers/oraclecloud/lib/service/service.py
Normal 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)
|
||||
96
prowler/providers/oraclecloud/models.py
Normal file
96
prowler/providers/oraclecloud/models.py
Normal 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
|
||||
1038
prowler/providers/oraclecloud/oci_provider.py
Normal file
1038
prowler/providers/oraclecloud/oci_provider.py
Normal file
File diff suppressed because it is too large
Load Diff
0
prowler/providers/oraclecloud/services/__init__.py
Normal file
0
prowler/providers/oraclecloud/services/__init__.py
Normal 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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user