diff --git a/compliance-script.md b/compliance-script.md deleted file mode 100644 index c8de336d..00000000 --- a/compliance-script.md +++ /dev/null @@ -1,424 +0,0 @@ -# Compliance Checking Script -1. [Overview](#overview) -1. [Setup](#setup) -1. [Output](#output) -1. [Usage](#usage) -1. [FAQ](#faq) - - -## Overview -The Compliance Checking script checks a tenancy's configuration against the CIS OCI Foundations Benchmark. In addition to CIS checks it can be check for alignment to OCI Best Practices by using the `--obp` flag. These checks review the following OCI best practices in your tenancy: -- Aggregation of OCI Audit compartment logs, Network Flow logs, and Object Storage logs are sent to Service Connector Hub in all regions -- A Budget for cost track is created in your tenancy -- Network connectivity to on-premises is redundant -- Cloud Guard is configured at the root compartment with detectors and responders - -The script is located under the *scripts* folder in this repository. It outputs a summary report CSV as well individual CSV findings report for configuration issues that are discovered in a folder(default location) with the region, tenancy name, and current day's date ex. ```-2022-12-02_13-50-30/```. - -## Setup - -### Required Permissions -The **Auditors Group** that is created as part of the CIS Landing Zone Terraform has all the permissions required to run the compliance checking in the tenancy. Below is the minimum OCI IAM Policy to grant a group the script in a tenancy. - -**Access to audit retention requires the user to be part of the Administrator group* - the only recommendation affected is CIS recommendation 3.1. - -``` -Allow group Auditor-Group to inspect all-resources in tenancy -Allow group Auditor-Group to read buckets in tenancy -Allow group Auditor-Group to read file-family in tenancy -Allow group Auditor-Group to read network-security-groups in tenancy -Allow group Auditor-Group to read users in tenancy -Allow group Auditor-Group to use cloud-shell in tenancy -Allow group Auditor-Group to read dynamic-groups in tenancy -Allow group Auditor-Group to read tag-defaults in tenancy -``` - -### Setup the script to run on a local machine -1. [Setup and Prerequisites](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) -1. Ensure your OCI `config` file is in the `~/.oci/` directory -1. Download cis_reports.py: [https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py](https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py) -``` -wget https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py -``` -1. Create a Python Virtual Environment with required modules -``` -python3 -m venv python-venv -source python-venv/bin/activate -pip3 install oci -pip3 install pytz -pip3 install requests -``` - -### Setup the script to run in a Cloud Shell Environment without a Python virtual environment -1. Download cis_reports.py: [https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py](https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py) -``` -wget https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py -``` - -### Setup the script to run in a Cloud Shell Environment with a Python virtual environment -1. Download cis_reports.py: [https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py](https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py) -``` -wget https://raw.githubusercontent.com/oracle-quickstart/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py -``` -1. Create a Python Virtual Environment with required modules -``` -python3 -m venv python-venv -source python-venv/bin/activate -pip3 install oci -pip3 install pytz -pip3 install requests -``` - - - -## Output -The script loops through all regions used by the tenancy and all resource types referenced in the CIS OCI Foundations Benchmark and outputs a summary compliance report. Each report row corresponds to a recommendation in the OCI Foundations Benchmark and identifies if the tenancy is in compliance as well as the number of offending findings. The report summary columns read as: - -- **Num**: the recommendation number in the CIS Benchmark document. -- **Level**: the recommendation level. Level 1 recommendations are less restrictive than Level 2. -- **Compliant**: whether the tenancy is in compliance with the recommendation. -- **Findings**: the number of offending findings for the recommendation. -- **Title**: the recommendation description. - -In the sample output below, we see the tenancy is not compliant with several recommendations. Among those is item 1.7 where the output shows 33 users do not have MFA enabled for accessing OCI Console. - -``` -########################################################################################## -# CIS Foundations Benchmark 2.0.0 Summary Report # -########################################################################################## -Num Level Compliant Findings Total Title -########################################################################################## -1.1 1 No 2 49 Ensure service level admins are created to manage resources of particular service -1.2 1 Yes 49 Ensure permissions on all resources are given only to the tenancy administrator group -1.3 1 Yes 49 Ensure IAM administrators cannot update tenancy Administrators group -1.4 1 Yes Ensure IAM password policy requires minimum length of 14 or greater -1.5 1 Yes 3 Ensure IAM password policy expires passwords within 365 days -1.6 1 No 1 3 Ensure IAM password policy prevents password reuse -1.7 1 No 9 18 Ensure MFA is enabled for all users with a console password -1.8 1 No 1 5 Ensure user API keys rotate within 90 days or less -1.9 1 Yes 2 Ensure user customer secret keys rotate within 90 days or less -1.10 1 No 1 3 Ensure user auth tokens rotate within 90 days or less -1.11 1 Yes Ensure user IAM Database Passwords rotate within 90 days -1.12 1 No 2 3 Ensure API keys are not created for tenancy administrator users -1.13 1 No 5 18 Ensure all OCI IAM user accounts have a valid and current email address -1.14 1 Yes 9 Ensure Instance Principal authentication is used for OCI instances, OCI Cloud Databases and OCI Functions to access OCI resources. -1.15 2 No 3 49 Ensure storage service-level admins cannot delete resources they manage -2.1 1 No 3 15 Ensure no security lists allow ingress from 0.0.0.0/0 to port 22. -2.2 1 Yes 15 Ensure no security lists allow ingress from 0.0.0.0/0 to port 3389. -2.3 1 No 1 11 Ensure no network security groups allow ingress from 0.0.0.0/0 to port 22. -2.4 1 Yes 11 Ensure no network security groups allow ingress from 0.0.0.0/0 to port 3389. -2.5 1 No 3 7 Ensure the default security list of every VCN restricts all traffic except ICMP. -2.6 1 Yes 1 Ensure Oracle Integration Cloud (OIC) access is restricted to allowed sources. -2.7 1 Yes 1 Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network. -2.8 1 Yes Ensure Oracle Autonomous Shared Database (ADB) access is restricted or deployed within a VCN. -3.1 1 Yes Ensure Compute Instance Legacy Metadata service endpoint is disabled. -3.2 2 Yes Ensure Secure Boot is enabled on Compute Instance. -3.3 2 Yes Ensure Compute Instance Legacy MetaData service endpoint is disabled. -4.1 1 Yes 2 Ensure default tags are used on resources. -4.2 1 Yes 5 Create at least one notification topic and subscription to receive monitoring alerts. -4.3 1 No Ensure a notification is configured for Identity Provider changes. -4.4 1 Yes Ensure a notification is configured for IdP group mapping changes. -4.5 1 Yes Ensure a notification is configured for IAM group changes. -4.6 1 Yes Ensure a notification is configured for IAM policy changes. -4.7 1 Yes Ensure a notification is configured for user changes. -4.8 1 Yes Ensure a notification is configured for VCN changes. -4.9 1 Yes Ensure a notification is configured for changes to route tables. -4.10 1 Yes Ensure a notification is configured for security list changes. -4.11 1 Yes Ensure a notification is configured for network security group changes. -4.12 1 Yes Ensure a notification is configured for changes to network gateways. -4.13 2 No 2 11 Ensure VCN flow logging is enabled for all subnets. -4.14 1 Yes Ensure Cloud Guard is enabled in the root compartment of the tenancy. -4.15 2 Yes Ensure a notification is configured for Oracle Cloud Guard problems detected. -4.16 1 Yes 1 Ensure customer created Customer Managed Key (CMK) is rotated at least annually. -4.17 2 No 3 3 Ensure write level Object Storage logging is enabled for all buckets. -5.1.1 1 Yes 3 Ensure no Object Storage buckets are publicly visible. -5.1.2 2 No 3 3 Ensure Object Storage Buckets are encrypted with a Customer Managed Key (CMK). -5.1.3 2 No 3 3 Ensure Versioning is Enabled for Object Storage Buckets. -5.2.1 2 Yes Ensure Block Volumes are encrypted with Customer Managed Keys. -5.2.2 2 Yes Ensure Boot Volumes are encrypted with Customer Managed Key. -5.3.1 2 Yes Ensure File Storage Systems are encrypted with Customer Managed Keys. -6.1 1 Yes 70 Create at least one compartment in your tenancy to store cloud resources. -6.2 1 No 3 65 Ensure no resources are created in the root compartment. -``` -For each non-compliant report item, a file with findings details is generated, as shown in the last part of the output: -``` -########################################################################################## -# Writing CIS reports to CSV # -########################################################################################## -CSV: summary_report --> tenancy2-2024-02-23_15-12-00/cis_summary_report.csv -HTML: html_summary_report --> tenancy2-2024-02-23_15-12-00/cis_html_summary_report.html -CSV: Identity and Access Management_1.1 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-1.csv -CSV: Identity and Access Management_1.6 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-6.csv -CSV: Identity and Access Management_1.7 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-7.csv -CSV: Identity and Access Management_1.8 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-8.csv -CSV: Identity and Access Management_1.10 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-10.csv -CSV: Identity and Access Management_1.12 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-12.csv -CSV: Identity and Access Management_1.13 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-13.csv -CSV: Identity and Access Management_1.15 --> tenancy2-2024-02-23_15-12-00/cis_Identity_and_Access_Management_1-15.csv -CSV: Networking_2.1 --> tenancy2-2024-02-23_15-12-00/cis_Networking_2-1.csv -CSV: Networking_2.3 --> tenancy2-2024-02-23_15-12-00/cis_Networking_2-3.csv -CSV: Networking_2.5 --> tenancy2-2024-02-23_15-12-00/cis_Networking_2-5.csv -CSV: Logging and Monitoring_4.13 --> tenancy2-2024-02-23_15-12-00/cis_Logging_and_Monitoring_4-13.csv -CSV: Logging and Monitoring_4.17 --> tenancy2-2024-02-23_15-12-00/cis_Logging_and_Monitoring_4-17.csv -CSV: Storage - Object Storage_5.1.2 --> tenancy2-2024-02-23_15-12-00/cis_Storage_Object_Storage_5-1-2.csv -CSV: Storage - Object Storage_5.1.3 --> tenancy2-2024-02-23_15-12-00/cis_Storage_Object_Storage_5-1-3.csv -CSV: Asset Management_6.2 --> tenancy2-2024-02-23_15-12-00/cis_Asset_Management_6-2.csv -``` -Back to our example, by looking at *cis_Identity and Access Management_1.7.csv* file, the output shows the 33 users who do not have MFA enabled for accessing OCI Console. The script only identifies compliance gaps. It does not remediate the findings. Administrator action is required to address this compliance gap. - -#### **Output Non-compliant Findings Only** - -Using --print-to-screen ```False``` will only print non-compliant findings to the screen. - -In the sample output below: - -``` -########################################################################################## -# CIS Foundations Benchmark 2.0.0 Summary Report # -########################################################################################## -Num Level Compliant Findings Total Title -########################################################################################## -1.1 1 No 2 49 Ensure service level admins are created to manage resources of particular service -1.6 1 No 1 3 Ensure IAM password policy prevents password reuse -1.7 1 No 9 18 Ensure MFA is enabled for all users with a console password -1.8 1 No 1 5 Ensure user API keys rotate within 90 days or less -1.10 1 No 1 3 Ensure user auth tokens rotate within 90 days or less -1.12 1 No 2 3 Ensure API keys are not created for tenancy administrator users -1.13 1 No 5 18 Ensure all OCI IAM user accounts have a valid and current email address -1.15 2 No 3 49 Ensure storage service-level admins cannot delete resources they manage -2.1 1 No 3 15 Ensure no security lists allow ingress from 0.0.0.0/0 to port 22. -2.3 1 No 1 11 Ensure no network security groups allow ingress from 0.0.0.0/0 to port 22. -2.5 1 No 3 7 Ensure the default security list of every VCN restricts all traffic except ICMP. -4.3 1 No Ensure a notification is configured for Identity Provider changes. -4.13 2 No 2 11 Ensure VCN flow logging is enabled for all subnets. -4.17 2 No 3 3 Ensure write level Object Storage logging is enabled for all buckets. -5.1.2 2 No 3 3 Ensure Object Storage Buckets are encrypted with a Customer Managed Key (CMK). -5.1.3 2 No 3 3 Ensure Versioning is Enabled for Object Storage Buckets. -6.2 1 No 3 65 Ensure no resources are created in the root compartment. -``` - -#### **Output Level 1 Findings Only** - -Using --level ```1``` will only print Level 1 findings. - -In the sample output below: -``` -########################################################################################## -# CIS Foundations Benchmark 2.0.0 Summary Report # -########################################################################################## -Num Level Compliant Findings Total Title -########################################################################################## -1.1 1 No 2 49 Ensure service level admins are created to manage resources of particular service -1.2 1 Yes 49 Ensure permissions on all resources are given only to the tenancy administrator group -1.3 1 Yes 49 Ensure IAM administrators cannot update tenancy Administrators group -1.4 1 Yes Ensure IAM password policy requires minimum length of 14 or greater -1.5 1 Yes 3 Ensure IAM password policy expires passwords within 365 days -1.6 1 No 1 3 Ensure IAM password policy prevents password reuse -1.7 1 No 9 18 Ensure MFA is enabled for all users with a console password -1.8 1 No 1 5 Ensure user API keys rotate within 90 days or less -1.9 1 Yes 2 Ensure user customer secret keys rotate within 90 days or less -1.10 1 No 1 3 Ensure user auth tokens rotate within 90 days or less -1.11 1 Yes Ensure user IAM Database Passwords rotate within 90 days -1.12 1 No 2 3 Ensure API keys are not created for tenancy administrator users -1.13 1 No 5 18 Ensure all OCI IAM user accounts have a valid and current email address -1.14 1 Yes 9 Ensure Instance Principal authentication is used for OCI instances, OCI Cloud Databases and OCI Functions to access OCI resources. -2.1 1 No 3 15 Ensure no security lists allow ingress from 0.0.0.0/0 to port 22. -2.2 1 Yes 15 Ensure no security lists allow ingress from 0.0.0.0/0 to port 3389. -2.3 1 No 1 11 Ensure no network security groups allow ingress from 0.0.0.0/0 to port 22. -2.4 1 Yes 11 Ensure no network security groups allow ingress from 0.0.0.0/0 to port 3389. -2.5 1 No 3 7 Ensure the default security list of every VCN restricts all traffic except ICMP. -2.6 1 Yes 1 Ensure Oracle Integration Cloud (OIC) access is restricted to allowed sources. -2.7 1 Yes 1 Ensure Oracle Analytics Cloud (OAC) access is restricted to allowed sources or deployed within a Virtual Cloud Network. -2.8 1 Yes Ensure Oracle Autonomous Shared Database (ADB) access is restricted or deployed within a VCN. -3.1 1 Yes Ensure Compute Instance Legacy Metadata service endpoint is disabled. -4.1 1 Yes 2 Ensure default tags are used on resources. -4.2 1 Yes 5 Create at least one notification topic and subscription to receive monitoring alerts. -4.3 1 No Ensure a notification is configured for Identity Provider changes. -4.4 1 Yes Ensure a notification is configured for IdP group mapping changes. -4.5 1 Yes Ensure a notification is configured for IAM group changes. -4.6 1 Yes Ensure a notification is configured for IAM policy changes. -4.7 1 Yes Ensure a notification is configured for user changes. -4.8 1 Yes Ensure a notification is configured for VCN changes. -4.9 1 Yes Ensure a notification is configured for changes to route tables. -4.10 1 Yes Ensure a notification is configured for security list changes. -4.11 1 Yes Ensure a notification is configured for network security group changes. -4.12 1 Yes Ensure a notification is configured for changes to network gateways. -4.14 1 Yes Ensure Cloud Guard is enabled in the root compartment of the tenancy. -4.16 1 Yes 1 Ensure customer created Customer Managed Key (CMK) is rotated at least annually. -5.1.1 1 Yes 3 Ensure no Object Storage buckets are publicly visible. -6.1 1 Yes 70 Create at least one compartment in your tenancy to store cloud resources. -6.2 1 No 3 65 Ensure no resources are created in the root compartment. -``` - -#### **Output OCI Best Practice Summary Report** -Using `--obp` will check for a tenancy's alignment to the available OCI Best Practices. - -``` -########################################################################################## -# OCI Best Practices Findings # -########################################################################################## -Category Compliant Findings -########################################################################################## -Cost_Tracking_Budgets True 1 -SIEM_Audit_Log_All_Comps True 0 -SIEM_Audit_Incl_Sub_Comp True 0 -SIEM_VCN_Flow_Logging False 511 -SIEM_Write_Bucket_Logs False 180 -SIEM_Read_Bucket_Logs False 161 -Networking_Connectivity False 17 -Cloud_Guard_Config False 1 -``` - - -## Usage - -### Arguments -``` -% python3 cis_reports.py -h -usage: cis_reports.py [-h] [-c FILE_LOCATION] [-t CONFIG_PROFILE] [-p PROXY] [--output-to-bucket OUTPUT_BUCKET] [--report-directory REPORT_DIRECTORY] - [--print-to-screen PRINT_TO_SCREEN] [--level LEVEL] [--regions REGIONS] [--raw] [--obp] [--all-resources] [--redact_output] [-ip] [-dt] [-st] [-v] [--debug] - -options: - -h, --help show this help message and exit - -c FILE_LOCATION OCI config file location - -t CONFIG_PROFILE Config file section to use (tenancy profile) - -p PROXY Set Proxy (i.e. www-proxy-server.com:80) - --output-to-bucket OUTPUT_BUCKET Set Output bucket name (i.e. my-reporting-bucket) - --report-directory REPORT_DIRECTORY Set Output report directory by default it is the current date (i.e. reports-date) - --print-to-screen PRINT_TO_SCREEN Set to False if you want to see only non-compliant findings (i.e. False) - --level LEVEL CIS Recommendation Level options are: 1 or 2. Set to 2 by default - --regions REGIONS Regions to run the compliance checks on, by default it will run in all regions. Sample input: us-ashburn-1,ca-toronto-1,eu-frankfurt-1 - --raw Outputs all resource data into CSV files - --obp Checks for OCI best practices - --all-resources Uses Advanced Search Service to query all resources in the tenancy and outputs to a JSON. This also enables OCI Best Practice Checks (--obp) and All resource to csv (--raw) flags. - --redact_output Redacts OCIDs in output CSV and JSON files - -ip Use Instance Principals for Authentication - -dt Use Delegation Token for Authentication in Cloud Shell - -st Authenticate using Security Token - -v Show the version of the script and exit. - --debug Enables debugging messages. This feature is in beta -% -``` - -### Usage Examples - -#### Executing in Cloud Shell to check CIS and OCI Best Practices with raw data -To run using Cloud Shell in all regions and check for OCI Best Practices with raw data of all resources output to CSV files and network topology. -``` -% python3 cis_reports.py -dt --obp --raw - -#### Executing in Cloud Shell to check CIS, OCI Best Practices with raw data, and get all resource via the Advanced Search Query service -To run using Cloud Shell in all regions and check for OCI Best Practices with raw data, network topology and get all resource via the Advanced Search Query service -``` -% python3 cis_reports.py -dt --all-resources -``` - -#### Executing on local machine with a specific OCI Config file - -To run on a local machine using a specific OCI Config file. -``` -% python3 cis_reports.py -c -``` -where `````` is the fully qualified path to an OCI client config file (default location is `~/.oci/config`). An OCI config file contains profiles that define the connecting parameters to your tenancy, like tenancy id, region, user id, fingerprint and key file. For more information: [https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm#SDK_and_CLI_Configuration_File](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm#SDK_and_CLI_Configuration_File). - - - [] - tenancy= - region=us-ashburn-1 - user= - fingerprint= - key_file=/path_to_my_private_key_file.pem - - -#### Executing on local machine with a specific profile - -To run on a local machine using a specific profile in the an OCI Config file. -``` -% python3 cis_reports.py -t -``` - -where `````` is the profile name in OCI client config file (typically located under $HOME/.oci). A profile defines the connecting parameters to your tenancy, like tenancy id, region, user id, fingerprint and key file. - - [] - tenancy= - region=us-ashburn-1 - user= - fingerprint= - key_file=/path_to_my_private_key_file.pem - -#### Executing on a local machine via Security Token (oci session authenticate) - -To run on a local machine using a Security Token without OCI Config file. For more information: [https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clitoken.htm](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clitoken.htm) - -Execute the oci command. -``` -% oci session authenticate -``` -This command will prompt for Region details, provide region name ex: us-ashburn-1. -Browser will open the OCI console window and asks for user credentials. Once after providing the credentials get back to the command prompt. This will create config file using the provided credentials in the "/Users/***/.oci/sessions/config. - -Execute the python script. -``` -% python3 cis_reports.py -st -``` - -#### Executing using Cloud Shell -To run in Cloud Shell with delegated token authentication. -``` -% python3 cis_reports.py -dt' -``` - -#### Executing on local machine with using instance principal -To run on an OCI instance that associated with Instance Principal. -``` -% python3 cis_reports.py -ip' -``` - -#### Executing using Cloud Shell in only two regions -To run on using Cloud Shell in on us-ashburn-1 and us-phoenix-1. IAM checks will performed in the tenancy's Home Region. -``` -% python3 cis_reports.py -dt --region us-ashburn-1,us-phoenix-1' -``` - -#### Executing using output to an bucket -To write the output files to an Object Storage bucket. - ``` -% python3 cis_reports.py --output-to-bucket 'my-example-bucket-1' -``` -Using --output-to-bucket `````` the reports will be copied to the Object Storage bucket in a folder(default location) with the region, tenancy name, and current day's date ex. ```us-ashburn-1--2020-12-08```. The bucket must already exist and the user executing the script must have permissions to use the bucket. If using the --report-directory flag as well the folder must already exist in the bucket. - -#### Executing using report directory -To write the output files to an specified directory. - ``` -% python3 cis_reports.py --report-directory 'my-directory' -``` -Using --report-directory `````` the reports will be copied to the specified directory. The directory must already exist. - -#### Executing using report directory and output to a bucket -To write the output files to an specified directory in an object storage bucket. - ``` -% python3 cis_reports.py --report-directory 'bucket-directory' --output-to-bucket 'my-example-bucket-1' -``` -Using --report-directory `````` and --output-to-bucket `````` together the reports will be copied to the specified directory in the specified bucket. The bucket must already exist in the **Tenancy's Home Region** and user must have permissions to write to that bucket. - -#### Executing using report directory and output to a bucket -To write the output files to an specified directory in an object storage bucket. - ``` -% python3 cis_reports.py --report-directory 'bucket-directory' --output-to-bucket 'my-example-bucket-1' -``` -Using --report-directory `````` and --output-to-bucket `````` together the reports will be copied to the specified directory in the specified bucket. The bucket must already exist in the **Tenancy's Home Region** and user must have permissions to write to that bucket. - -#### Executing on local machine and output raw data -To run on a local machine with the default profile and output raw data as well as the reports. -``` -% python3 cis_reports.py --raw -``` - -## FAQ - -1. Why did the script may fail to run when executing from the local machine with message "** OCI_CONFIG_FILE and OCI_CONFIG_PROFILE env variables not found, abort. ***"? - * In this case, make sure to set OCI_CONFIG_HOME to the full path of .oci/config file. Optionally, OCI_CONFIG_PROFILE can be configured with a default profile to use from config. - * Optionally, you can use the `-c` option to specify an alternate config file -1. `ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips 26 Jan 2017'.` - * Change your urllib3 with the following `pip install --upgrade 'urllib3<=2' --user` - diff --git a/release-notes.md b/release-notes.md index e79ce14e..3620c693 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,21 @@ +# March 25, 2024 Release Notes - 2.8.1 +1. [Updates/Fixes to the CIS Compliance Script](#2-8-1-script-updates) + +## Updates/Fixes to the CIS Compliance Script +**Updates:** +- Added flag `--report-prefix` to allow unique files for better baseline comparison. +- Improved performance in querying Identity Domains users’ API keys. +- Improved Identity Domains checking for federated users by using is_federated flag. +- Added Deep Link with Identity Domain name to user, group, and dynamic group records. +- The audit configuration check has been removed because it is no longer in the benchmark. +- Boot Volume resources were added to the check 6.2 resources in the root compartment. + +**Fixes:** +- Handling KMS keys with date issues. +- Removed duplication of Identity Groups for Identity Domains. +- Consistency and commenting updates. + + # February 23, 2024 Release Notes - 2.8.0 1. [Updates for CIS OCI Benchmark 2.0.0](#2-8-0-cis-2-updates) 1. [Compliance Checker Script Update for Identity Domains](#2-8-0-cis-2-iddomains) diff --git a/scripts/cis_reports.py b/scripts/cis_reports.py index 659650ef..a1431379 100644 --- a/scripts/cis_reports.py +++ b/scripts/cis_reports.py @@ -1,5 +1,5 @@ ########################################################################## -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. # This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license. # # cis_reports.py @@ -35,9 +35,9 @@ except Exception: OUTPUT_TO_XLSX = False -RELEASE_VERSION = "2.8.0" -PYTHON_SDK_VERSION = "2.120.0" -UPDATED_DATE = "February 23, 2024" +RELEASE_VERSION = "2.8.1" +PYTHON_SDK_VERSION = "2.124.1" +UPDATED_DATE = "March 25, 2024" ########################################################################## @@ -138,7 +138,7 @@ class CIS_Report: str_kms_key_time_max_datetime = kms_key_time_max_datetime.strftime(__iso_time_format) kms_key_time_max_datetime = datetime.datetime.strptime(str_kms_key_time_max_datetime, __iso_time_format) - def __init__(self, config, signer, proxy, output_bucket, report_directory, print_to_screen, regions_to_run_in, raw_data, obp, redact_output, debug=False, all_resources=True): + def __init__(self, config, signer, proxy, output_bucket, report_directory, report_prefix, report_summary_json, print_to_screen, regions_to_run_in, raw_data, obp, redact_output, debug=False, all_resources=True): # CIS Foundation benchmark 2.0.0 self.cis_foundations_benchmark_2_0 = { @@ -169,7 +169,7 @@ def __init__(self, config, signer, proxy, output_bucket, report_directory, print '3.1': {'section': 'Compute', 'recommendation_#': '3.1', 'Title': 'Ensure Compute Instance Legacy Metadata service endpoint is disabled.', 'Status': True, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': ['4.6'], 'CCCS Guard Rail': '', 'Remediation': []}, '3.2': {'section': 'Compute', 'recommendation_#': '3.2', 'Title': 'Ensure Secure Boot is enabled on Compute Instance.', 'Status': True, 'Level': 2, 'Total': [], 'Findings': [], 'CISv8': ['4.1'], 'CCCS Guard Rail': '', 'Remediation': []}, - '3.3': {'section': 'Compute', 'recommendation_#': '3.2', 'Title': 'Ensure Compute Instance Legacy MetaData service endpoint is disabled.', 'Status': True, 'Level': 2, 'Total': [], 'Findings': [], 'CISv8': [''], 'CCCS Guard Rail': '', 'Remediation': []}, + '3.3': {'section': 'Compute', 'recommendation_#': '3.3', 'Title': 'Ensure In-transit Encryption is enabled on Compute Instance.', 'Status': True, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': [''], 'CCCS Guard Rail': '', 'Remediation': []}, '4.1': {'section': 'Logging and Monitoring', 'recommendation_#': '4.1', 'Title': 'Ensure default tags are used on resources.', 'Status': False, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': ['1.1'], 'CCCS Guard Rail': '', 'Remediation': []}, '4.2': {'section': 'Logging and Monitoring', 'recommendation_#': '4.2', 'Title': 'Create at least one notification topic and subscription to receive monitoring alerts.', 'Status': False, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': ['8.2', '8.11'], 'CCCS Guard Rail': '11', 'Remediation': []}, @@ -196,7 +196,6 @@ def __init__(self, config, signer, proxy, output_bucket, report_directory, print '5.2.2': {'section': 'Storage - Block Volumes', 'recommendation_#': '5.2.2', 'Title': 'Ensure Boot Volumes are encrypted with Customer Managed Key.', 'Status': True, 'Level': 2, 'Total': [], 'Findings': [], 'CISv8': ['3.11'], 'CCCS Guard Rail': ''}, '5.3.1': {'section': 'Storage - File Storage Service', 'recommendation_#': '5.3.1', 'Title': 'Ensure File Storage Systems are encrypted with Customer Managed Keys.', 'Status': True, 'Level': 2, 'Total': [], 'Findings': [], 'CISv8': ['3.11'], 'CCCS Guard Rail': '', 'Remediation': []}, - '6.1': {'section': 'Asset Management', 'recommendation_#': '6.1', 'Title': 'Create at least one compartment in your tenancy to store cloud resources.', 'Status': True, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': ['3.1'], 'CCCS Guard Rail': '2,3,8,12', 'Remediation': []}, '6.2': {'section': 'Asset Management', 'recommendation_#': '6.2', 'Title': 'Ensure no resources are created in the root compartment.', 'Status': True, 'Level': 1, 'Total': [], 'Findings': [], 'CISv8': ['3.12'], 'CCCS Guard Rail': '1,2,3', 'Remediation': []} } @@ -942,10 +941,10 @@ def __init__(self, config, signer, proxy, output_bucket, report_directory, print self.__raw_regions.append(record) # By Default it is today's date - if report_directory: - self.__report_directory = report_directory + "/" - else: - self.__report_directory = self.__tenancy.name + "-" + self.report_datetime + self.__report_directory = f'{report_directory}/' if report_directory else f'{self.__tenancy.name}-{self.report_datetime}' + + self.__report_prefix = f'{report_prefix}_' if report_prefix else '' + self.__report_summary_json = report_summary_json # Checking if a Tenancy has Identity Domains enabled try: @@ -956,7 +955,7 @@ def __init__(self, config, signer, proxy, output_bucket, report_directory, print except Exception as e: # To be safe if it fails I'll check self.__identity_domains_enabled = True - debug("__init__: Exception checking identity domains status \n" + str(e)) + debug("__init__: Exception checking identity domains status\n" + str(e)) self.__errors.append({"id" : "__init__", "error" : str(e)}) @@ -998,7 +997,7 @@ def __init__(self, config, signer, proxy, output_bucket, report_directory, print def __create_regional_signers(self, proxy): print("Creating regional signers and configs...") for region_key, region_values in self.__regions.items(): - debug("processing __create_regional_signers ") + debug("processing __create_regional_signers") # Creating regional configs and signers region_signer = self.__signer region_signer.region_name = region_key @@ -1142,7 +1141,7 @@ def __identity_read_compartments(self): # Need to convert for raw output for compartment in self.__compartments: - debug("__identity_read_compartments: Getting Compartments:" + compartment.name) + debug("__identity_read_compartments: Getting Compartments: " + compartment.name) deep_link = self.__oci_compartment_uri + compartment.id record = { 'id': compartment.id, @@ -1187,7 +1186,7 @@ def __identity_read_compartments(self): return self.__compartments except Exception as e: - debug("__identity_read_compartments: Error Getting Compartments:" + compartment.name) + debug("__identity_read_compartments: Error Getting Compartments: " + compartment.name) self.__errors.append({"id" : "__identity_read_compartments", "error" : str(e)}) raise RuntimeError( "Error in identity_read_compartments: " + str(e.args)) @@ -1203,7 +1202,7 @@ def __identity_read_domains(self): # Finding all Identity Domains in the tenancy for compartment in self.__compartments: try: - debug("__identity_read_domains: Getting Identity Domains for Compartment :" + str(compartment.name)) + debug("__identity_read_domains: Getting Identity Domains for Compartment: " + str(compartment.name)) raw_identity_domains += oci.pagination.list_call_get_all_results( self.__regions[self.__home_region]['identity_client'].list_domains, @@ -1212,14 +1211,14 @@ def __identity_read_domains(self): ).data except Exception as e: - debug("__identity_read_domains: Exception collecting Identity Domains \n" + str(e)) + debug("__identity_read_domains: Exception collecting Identity Domains\n" + str(e)) # If this fails the tenancy likely doesn't have identity domains or the permissions are off for domain in raw_identity_domains: debug("__identity_read_domains: Getting password policy for domain: " + domain.display_name) - domain_dict = oci.util.to_dict(domain) + domain_dict = oci.util.to_dict(domain) try: - debug("__identity_read_domains: Getting Identity Domain Password Policy for :" + domain.display_name) + debug("__identity_read_domains: Getting Identity Domain Password Policy for: " + domain.display_name) idcs_url = domain.url + "/admin/v1/PasswordPolicies/PasswordPolicy" raw_pwd_policy_resp = requests.get(url=idcs_url, auth=self.__signer) raw_pwd_policy_dict = json.loads(raw_pwd_policy_resp.content) @@ -1257,42 +1256,44 @@ def __identity_read_groups_and_membership(self): debug("processing __identity_read_groups_and_membership for Identity Domains Enabled Tenancy") for identity_domain in self.__identity_domains: debug("processing __identity_read_groups_and_membership for Identity Domain: " + identity_domain['display_name']) + id_domain_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] try: groups_data = self.__identity_domains_get_all_results(func=identity_domain['IdentityDomainClient'].list_groups, args={}) for grp in groups_data: debug("\t__identity_read_groups_and_membership: reading group data " + str(grp.display_name)) grp_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + "/groups/" + grp.ocid - for grp in groups_data: - if not grp.members: - debug("\t\t__identity_read_groups_and_membership: Adding group with no members " + str(grp.display_name)) - + if not grp.members: + debug("\t\t__identity_read_groups_and_membership: Adding group with no members " + str(grp.display_name)) + + group_record = { + "id": grp.ocid, + "name": grp.display_name, + "deep_link": self.__generate_csv_hyperlink(grp_deep_link, grp.display_name), + "domain_deeplink" : self.__generate_csv_hyperlink(id_domain_deep_link, identity_domain['display_name']), + "description": grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group.description if grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group else None, + "time_created" : self.get_date_iso_format(grp.meta.created), + "user_id": "", + "user_id_link": "" + } + # Adding a record per empty group + self.__groups_to_users.append(group_record) + else: + # For groups with members print one record per user per group + for member in grp.members: + debug("\t__identity_read_groups_and_membership: reading members data in group" + str(grp.display_name)) + user_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + "/users/" + member.ocid group_record = { - "id": grp.ocid, + "id": grp.id, "name": grp.display_name, "deep_link": self.__generate_csv_hyperlink(grp_deep_link, grp.display_name), + "domain_deeplink" : self.__generate_csv_hyperlink(id_domain_deep_link, identity_domain['display_name']), "description": grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group.description if grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group else None, "time_created" : self.get_date_iso_format(grp.meta.created), - "user_id": "", - "user_id_link": "" + "user_id": member.ocid, + "user_id_link": self.__generate_csv_hyperlink(user_deep_link, member.name) } - # Adding a record per empty group + # Adding a record per user to group self.__groups_to_users.append(group_record) - else: - # For groups with members print one record per user per group - for member in grp.members: - debug("\t__identity_read_groups_and_membership: reading members data in group" + str(grp.display_name)) - user_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + "/users/" + member.ocid - group_record = { - "id": grp.id, - "name": grp.display_name, - "deep_link": self.__generate_csv_hyperlink(grp_deep_link, grp.display_name), - "description": grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group.description if grp.urn_ietf_params_scim_schemas_oracle_idcs_extension_group_group else None, - "time_created" : self.get_date_iso_format(grp.meta.created), - "user_id": member.ocid, - "user_id_link": self.__generate_csv_hyperlink(user_deep_link, member.name) - } - # Adding a record per user to group - self.__groups_to_users.append(group_record) except Exception as e: self.__errors.append({"id" : "__identity_read_groups_and_membership", "error" : str(e)}) @@ -1322,7 +1323,9 @@ def __identity_read_groups_and_membership(self): "id": grp.id, "name": grp.name, "deep_link": self.__generate_csv_hyperlink(grp_deep_link, grp.name), + "domain_deeplink" : "", "description": grp.description, + "domain_deeplink" : "", "lifecycle_state": grp.lifecycle_state, "time_created": grp.time_created.strftime(self.__iso_time_format), "user_id": "", @@ -1338,6 +1341,7 @@ def __identity_read_groups_and_membership(self): "id": grp.id, "name": grp.name, "deep_link": self.__generate_csv_hyperlink(grp_deep_link, grp.name), + "domain_deeplink" : "", "description": grp.description, "lifecycle_state": grp.lifecycle_state, "time_created": grp.time_created.strftime(self.__iso_time_format), @@ -1390,108 +1394,123 @@ def __identity_domains_get_all_results(self, func, args): ########################################################################## def __identity_read_users(self): debug(f'__identity_read_users: Getting User data for Identity Domains: {str(self.__identity_domains_enabled)}') - if self.__identity_domains_enabled: - for identity_domain in self.__identity_domains: + try: + if self.__identity_domains_enabled: + for identity_domain in self.__identity_domains: + try: + users_data = self.__identity_domains_get_all_results(func=identity_domain['IdentityDomainClient'].list_users, + args={}) + # Adding record to the users + for user in users_data: + deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + "/users/" + user.ocid + id_domain_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + record = { + 'id': user.ocid, + 'domain_deeplink' : self.__generate_csv_hyperlink(id_domain_deep_link, identity_domain['display_name']), + 'name': user.user_name, + 'deep_link': self.__generate_csv_hyperlink(deep_link, user.user_name), + 'defined_tags': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_oci_tags.defined_tags if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_oci_tags else None, + 'description': user.description, + 'email': user.emails[0].value if user.emails else None, + 'email_verified': user.emails[0].verified if user.emails else None, + 'external_identifier': user.external_id, + 'is_federated': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_user_user.is_federated_user, + 'is_mfa_activated': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_mfa_user.mfa_status if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_mfa_user else None, + 'lifecycle_state': user.active, + 'time_created': user.meta.created, + 'can_use_api_keys': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_api_keys if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_auth_tokens': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_auth_tokens if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_console_password': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_console_password if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_customer_secret_keys': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_customer_secret_keys if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_db_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_db_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_o_auth2_client_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_o_auth2_client_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'can_use_smtp_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_smtp_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'groups': [] + } + # Adding Groups to the user + for group in self.__groups_to_users: + if user.ocid == group['user_id']: + record['groups'].append(group['name']) + if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_user_credentials_user: + debug("__identity_read_users: Collecting user API Key for user: " + str(user.user_name)) + record['api_keys'] = self.__identity_read_user_api_key(user_ocid=user.ocid, identity_domain=identity_domain) + record['auth_tokens'] = self.__identity_read_user_auth_token(user.ocid, identity_domain=identity_domain) + record['customer_secret_keys'] = self.__identity_read_user_customer_secret_key(user.ocid, identity_domain=identity_domain) + record['database_passowrds'] = self.__identity_read_user_database_password(user.ocid,identity_domain=identity_domain) + else: + debug("__identity_read_users: skipping user API Key collection for user: " + str(user.user_name)) + record['api_keys'] = None + record['auth_tokens'] = None + record['customer_secret_keys'] = None + record['database_passowrds'] = None + self.__users.append(record) + + except Exception as e: + debug("__identity_read_users: Identity Domains are : " + str(self.__identity_domains_enabled)) + self.__errors.append({'id' : "__identity_read_users", 'error' : str(e)}) + raise RuntimeError( + "Error in __identity_read_users: " + str(e)) + + print("\tProcessed " + str(len(self.__users)) + " Users") + return self.__users + + else: try: - users_data = self.__identity_domains_get_all_results(func=identity_domain['IdentityDomainClient'].list_users, - args={}) + # Getting all users in the Tenancy + users_data = oci.pagination.list_call_get_all_results( + self.__regions[self.__home_region]['identity_client'].list_users, + compartment_id=self.__tenancy.id + ).data + # Adding record to the users for user in users_data: - deep_link = self.__oci_identity_domains_uri + identity_domain['id'] + "/users/" + user.ocid + deep_link = self.__oci_users_uri + user.id record = { - 'id': user.ocid, - 'name': user.user_name, - 'deep_link': self.__generate_csv_hyperlink(deep_link, user.user_name), - 'defined_tags': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_oci_tags.defined_tags if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_oci_tags else None, + 'id': user.id, + 'domain_deeplink' : "", + 'name': user.name, + 'deep_link': self.__generate_csv_hyperlink(deep_link, user.name), + 'defined_tags': user.defined_tags, 'description': user.description, - 'email': user.emails[0].value if user.emails else None, - 'email_verified': user.emails[0].verified if user.emails else None, - 'external_identifier': user.external_id, - 'identity_provider_id': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_user_user.provider, - 'is_mfa_activated': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_mfa_user.mfa_status if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_mfa_user else None, - 'lifecycle_state': user.active, - 'time_created': user.meta.created, - 'can_use_api_keys': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_api_keys if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_auth_tokens': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_auth_tokens if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_console_password': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_console_password if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_customer_secret_keys': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_customer_secret_keys if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_db_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_db_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_o_auth2_client_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_o_auth2_client_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, - 'can_use_smtp_credentials': user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user.can_use_smtp_credentials if user.urn_ietf_params_scim_schemas_oracle_idcs_extension_capabilities_user else None, + 'email': user.email, + 'email_verified': user.email_verified, + 'external_identifier': user.external_identifier, + 'is_federated': True if user.identity_provider_id is not None else False, + 'is_mfa_activated': user.is_mfa_activated, + 'lifecycle_state': True if user.lifecycle_state == 'ACTIVE' else False, + 'time_created': user.time_created.strftime(self.__iso_time_format), + 'can_use_api_keys': user.capabilities.can_use_api_keys, + 'can_use_auth_tokens': user.capabilities.can_use_auth_tokens, + 'can_use_console_password': user.capabilities.can_use_console_password, + 'can_use_customer_secret_keys': user.capabilities.can_use_customer_secret_keys, + 'can_use_db_credentials': user.capabilities.can_use_db_credentials, + 'can_use_o_auth2_client_credentials': user.capabilities.can_use_o_auth2_client_credentials, + 'can_use_smtp_credentials': user.capabilities.can_use_smtp_credentials, 'groups': [] } # Adding Groups to the user for group in self.__groups_to_users: - if user.ocid == group['user_id']: + if user.id == group['user_id']: record['groups'].append(group['name']) - record['api_keys'] = self.__identity_read_user_api_key(user_ocid=user.ocid, identity_domain=identity_domain) - record['auth_tokens'] = self.__identity_read_user_auth_token(user.ocid, identity_domain=identity_domain) - record['customer_secret_keys'] = self.__identity_read_user_customer_secret_key(user.ocid, identity_domain=identity_domain) - record['database_passowrds'] = self.__identity_read_user_database_password(user.ocid,identity_domain=identity_domain) + record['api_keys'] = self.__identity_read_user_api_key(user.id) + record['auth_tokens'] = self.__identity_read_user_auth_token( + user.id) + record['customer_secret_keys'] = self.__identity_read_user_customer_secret_key( + user.id) + record['database_passowrds'] = self.__identity_read_user_database_password(user.id) self.__users.append(record) + print("\tProcessed " + str(len(self.__users)) + " Users") + return self.__users except Exception as e: - debug("__identity_read_users: Identity Domains are : " + str(self.__identity_domains_enabled)) - self.__errors.append({'id' : "__identity_read_users", 'error' : str(e)}) + debug("__identity_read_users: Error is: " + str(e)) + self.__errors.append({"id" : "__identity_read_users", "error" : str(e)}) raise RuntimeError( "Error in __identity_read_users: " + str(e)) - - print("\tProcessed " + str(len(self.__users)) + " Users") - return self.__users - - else: - try: - # Getting all users in the Tenancy - users_data = oci.pagination.list_call_get_all_results( - self.__regions[self.__home_region]['identity_client'].list_users, - compartment_id=self.__tenancy.id - ).data - - # Adding record to the users - for user in users_data: - deep_link = self.__oci_users_uri + user.id - record = { - 'id': user.id, - 'name': user.name, - 'deep_link': self.__generate_csv_hyperlink(deep_link, user.name), - 'defined_tags': user.defined_tags, - 'description': user.description, - 'email': user.email, - 'email_verified': user.email_verified, - 'external_identifier': user.external_identifier, - 'identity_provider_id': user.identity_provider_id, - 'is_mfa_activated': user.is_mfa_activated, - 'lifecycle_state': True if user.lifecycle_state == 'ACTIVE' else False, - 'time_created': user.time_created.strftime(self.__iso_time_format), - 'can_use_api_keys': user.capabilities.can_use_api_keys, - 'can_use_auth_tokens': user.capabilities.can_use_auth_tokens, - 'can_use_console_password': user.capabilities.can_use_console_password, - 'can_use_customer_secret_keys': user.capabilities.can_use_customer_secret_keys, - 'can_use_db_credentials': user.capabilities.can_use_db_credentials, - 'can_use_o_auth2_client_credentials': user.capabilities.can_use_o_auth2_client_credentials, - 'can_use_smtp_credentials': user.capabilities.can_use_smtp_credentials, - 'groups': [] - } - # Adding Groups to the user - for group in self.__groups_to_users: - if user.id == group['user_id']: - record['groups'].append(group['name']) - - record['api_keys'] = self.__identity_read_user_api_key(user.id) - record['auth_tokens'] = self.__identity_read_user_auth_token( - user.id) - record['customer_secret_keys'] = self.__identity_read_user_customer_secret_key( - user.id) - record['database_passowrds'] = self.__identity_read_user_database_password(user.id) - self.__users.append(record) - print("\tProcessed " + str(len(self.__users)) + " Users") - return self.__users - - except Exception as e: - debug("__identity_read_users: User ID is: " + str(user)) - raise RuntimeError( - "Error in __identity_read_users: " + str(e.args)) + except Exception as e: + raise RuntimeError( + "Error in __identity_read_users: " + str(e.args)) ########################################################################## # Load user api keys ########################################################################## @@ -1678,7 +1697,7 @@ def __identity_read_user_database_password(self, user_ocid, identity_domain=None ########################################################################## def __identity_read_tenancy_policies(self): try: - debug("__identity_read_tenancy_policies: Getting Tenancy policies :") + debug("__identity_read_tenancy_policies: Getting Tenancy policies: ") policies_data = oci.pagination.list_call_get_all_results( self.__regions[self.__home_region]['search_client'].search_resources, search_details=oci.resource_search.models.StructuredSearchDetails( @@ -1686,7 +1705,7 @@ def __identity_read_tenancy_policies(self): ).data for policy in policies_data: - debug("__identity_read_tenancy_policies: Reading Tenancy policies : " + policy.display_name) + debug("__identity_read_tenancy_policies: Reading Tenancy policies: " + policy.display_name) deep_link = self.__oci_policies_uri + policy.identifier record = { "id": policy.identifier, @@ -1702,7 +1721,7 @@ def __identity_read_tenancy_policies(self): return self.__policies except Exception as e: - debug("__identity_read_tenancy_policies: Exception reading Tenancy policies : " + policy.display_name) + debug("__identity_read_tenancy_policies: Exception reading Tenancy policies: " + policy.display_name) self.__errors.append({"id" : "__identity_read_tenancy_policies", "error" : str(e)}) raise RuntimeError("Error in __identity_read_tenancy_policies: " + str(e.args)) @@ -1716,11 +1735,13 @@ def __identity_read_dynamic_groups(self): for identity_domain in self.__identity_domains: dynamic_groups_data = self.__identity_domains_get_all_results(func=identity_domain['IdentityDomainClient'].list_dynamic_resource_groups, args={}) + id_domain_deep_link = self.__oci_identity_domains_uri + identity_domain['id'] for dynamic_group in dynamic_groups_data: debug("__identity_read_dynamic_groups: reading dynamic groups" + str(dynamic_group.display_name)) deep_link = self.__oci_identity_domains_uri + "/domains/" + identity_domain['id'] + "/dynamic-groups/" + dynamic_group.id record = oci.util.to_dict(dynamic_group) record['deep_link'] = self.__generate_csv_hyperlink(deep_link, dynamic_group.display_name) + record['domain_deeplink'] = self.__generate_csv_hyperlink(id_domain_deep_link, identity_domain['display_name']) self.__dynamic_groups.append(record) else: @@ -1733,7 +1754,7 @@ def __identity_read_dynamic_groups(self): debug("__identity_read_dynamic_groups: reading dynamic groups" + str(dynamic_group.name)) record = oci.util.to_dict(dynamic_group) record['deep_link'] = self.__generate_csv_hyperlink(deep_link, dynamic_group.name) - + record['domain_deeplink'] = None self.__dynamic_groups.append(record) print("\tProcessed " + str(len(self.__dynamic_groups)) + " Dynamic Groups") @@ -1749,9 +1770,9 @@ def __identity_read_dynamic_groups(self): ############################################ def __identity_read_availability_domains(self): try: - debug("__identity_read_availability_domains: Getting Availability Domains for regions :") + debug("__identity_read_availability_domains: Getting Availability Domains for regions:") for region_key, region_values in self.__regions.items(): - debug("__identity_read_availability_domains: reading Availability Domains for regions :" +region_key) + debug("__identity_read_availability_domains: reading Availability Domains for regions: " +region_key) region_values['availability_domains'] = oci.pagination.list_call_get_all_results( region_values['identity_client'].list_availability_domains, compartment_id=self.__tenancy.id @@ -1759,8 +1780,8 @@ def __identity_read_availability_domains(self): print("\tProcessed " + str(len(region_values['availability_domains'])) + " Availability Domains in " + region_key) except Exception as e: - debug("__identity_read_availability_domains: reading availability domain" + str(region_key)) - self.__errors.append({"id" : "__identity_read_availability_domains" + "_" + str(region_key), "error" : str(e)}) + debug("__identity_read_availability_domains: reading availability domain " + str(region_key)) + self.__errors.append({"id": "__identity_read_availability_domains" + "_" + str(region_key), "error": str(e)}) raise RuntimeError( "Error in __identity_read_availability_domains: " + str(e.args)) @@ -2635,7 +2656,9 @@ def __network_read_ip_sec_connections(self): ############################################ def __network_topology_dump(self): debug("__network_topology_dump: Starting") - + if type(self.__signer) == oci.auth.signers.InstancePrincipalsDelegationTokenSigner: + self.__errors.append({"id": "__network_topology_dump", "error": "Delegated Tokens via Cloud Shell not supported." }) + return def api_function(region_key, region_values, tenancy_id): try: get_vcn_topology_response = region_values['topology_client'].get_networking_topology( @@ -3379,25 +3402,6 @@ def __budget_read_budgets(self): raise RuntimeError( "Error in __budget_read_budgets " + str(e.args)) - ########################################################################## - # Audit Configuration - ########################################################################## - def __audit_read_tenancy_audit_configuration(self): - # Pulling the Audit Configuration - try: - self.__audit_retention_period = self.__regions[self.__home_region]['audit_client'].get_configuration( - self.__tenancy.id).data.retention_period_days - except Exception as e: - if "NotAuthorizedOrNotFound" in str(e): - self.__audit_retention_period = -1 - print("\t*** Access to audit retention requires the user to be part of the Administrator group ***") - self.__errors.append({"id" : self.__tenancy.id, "error" : "*** Access to audit retention requires the user to be part of the Administrator group ***"}) - else: - raise RuntimeError("Error in __audit_read_tenancy_audit_configuration " + str(e.args)) - - print("\tProcessed Audit Configuration.") - return self.__audit_retention_period - ########################################################################## # Cloud Guard Configuration ########################################################################## @@ -3648,7 +3652,7 @@ def __search_resources_in_root_compartment(self): # query = [] # resources_in_root_data = [] # record = [] - query_non_compliant = "query VCN, instance, volume, filesystem, bucket, autonomousdatabase, database, dbsystem resources where compartmentId = '" + self.__tenancy.id + "'" + query_non_compliant = "query VCN, instance, volume, bootvolume, filesystem, bucket, autonomousdatabase, database, dbsystem resources where compartmentId = '" + self.__tenancy.id + "'" query_all_resources = "query all resources where compartmentId = '" + self.__tenancy.id + "'" # resources_in_root_data = self.__search_run_structured_query(query) @@ -3687,7 +3691,7 @@ def __search_resources_in_root_compartment(self): } self.cis_foundations_benchmark_2_0['6.2']['Total'].append(record) except: - self.__errors.append({"id" : "search_resources_in_root_compartment Invalid OCID", "error" : str(item)}) + self.__errors.append({"id": "search_resources_in_root_compartment Invalid OCID", "error" : str(item)}) debug(f'__search_resources_in_root_compartment: Invalid OCID: {str(item)}') except Exception as e: @@ -3772,7 +3776,7 @@ def __core_instance_read_compute(self): # Returning Instances - print("\tProcessed " + str(len(self.__Instance)) + " Service Connectors") + print("\tProcessed " + str(len(self.__Instance)) + " Compute Instances") return self.__service_connectors except Exception as e: raise RuntimeError("Error in __core_instance_read_compute " + str(e.args)) @@ -3808,7 +3812,7 @@ def __report_cis_analyze_tenancy_data(self): # 1.3 Check - May want to add a service check for policy in self.__policies: - if policy['name'].upper() != "Tenant Admin Policy".upper() and policy['name'].upper() != "PSM-root-policy".upper(): + if policy['name'].lower() not in ['tenant admin policy', 'psm-root-policy']: for statement in policy['statements']: if ("allow group".upper() in statement.upper() and "tenancy".upper() in statement.upper() and ("to manage ".upper() in statement.upper() or "to use".upper() in statement.upper()) and ("all-resources".upper() in statement.upper() or (" groups ".upper() in statement.upper() and " users ".upper() in statement.upper()))): split_statement = statement.split("where") @@ -3877,7 +3881,7 @@ def __report_cis_analyze_tenancy_data(self): # 1.7 Check - Local Users w/o MFA for user in self.__users: - if user['identity_provider_id'] is None and user['can_use_console_password'] and not (user['is_mfa_activated']) and user['lifecycle_state']: + if not(user['is_federated']) and user['can_use_console_password'] and not (user['is_mfa_activated']) and user['lifecycle_state']: self.cis_foundations_benchmark_2_0['1.7']['Status'] = False self.cis_foundations_benchmark_2_0['1.7']['Findings'].append( user) @@ -3996,26 +4000,25 @@ def __report_cis_analyze_tenancy_data(self): # CIS 1.15 Check - Ensure storage service-level admins cannot delete resources they manage. # Iterating through all policies for policy in self.__policies: - if policy['name'].upper() != "Tenant Admin Policy".upper() and policy['name'].upper() != "PSM-root-policy": + if policy['name'].lower() not in ['tenant admin policy', 'psm-root-policy']: for statement in policy['statements']: for resource in self.cis_iam_checks['1.15']: - if "allow group".upper() in statement.upper() and "manage".upper() in statement.upper() and resource.upper() in statement.upper(): + if "allow group".upper() in statement.upper() and "to manage ".upper() in statement.upper() and resource.upper() in statement.upper(): split_statement = statement.split("where") if len(split_statement) == 2: clean_where_clause = split_statement[1].upper().replace(" ", "").replace("'", "") if all(permission.upper() in clean_where_clause for permission in self.cis_iam_checks['1.15'][resource]) and \ not(all(permission.upper() in clean_where_clause for permission in self.cis_iam_checks['1.15-storage-admin'][resource])): - debug("__report_cis_analyze_tenancy_data no permissions to delete storage : " + str(policy['name'])) - + debug("__report_cis_analyze_tenancy_data no permissions to delete storage: " + str(policy['name'])) pass # Checking if this is the Storage admin with allowed elif all(permission.upper() in clean_where_clause for permission in self.cis_iam_checks['1.15-storage-admin'][resource]) and \ not(all(permission.upper() in clean_where_clause for permission in self.cis_iam_checks['1.15'][resource])): - debug("__report_cis_analyze_tenancy_data storage admin policy is : " + str(policy['name'])) + debug("__report_cis_analyze_tenancy_data storage admin policy is: " + str(policy['name'])) pass else: self.cis_foundations_benchmark_2_0['1.15']['Findings'].append(policy) - debug("__report_cis_analyze_tenancy_data else policy is /n: " + str(policy['name'])) + debug("__report_cis_analyze_tenancy_data else policy is\n: " + str(policy['name'])) else: self.cis_foundations_benchmark_2_0['1.15']['Findings'].append(policy) @@ -4025,7 +4028,7 @@ def __report_cis_analyze_tenancy_data(self): else: self.cis_foundations_benchmark_2_0['1.15']['Status'] = True - # CIS Total 1.14 Adding - All IAM Policies for to CIS Total + # CIS Total 1.15 Adding - All IAM Policies for to CIS Total self.cis_foundations_benchmark_2_0['1.15']['Total'] = self.__policies # CIS 2.1, 2.2, & 2.5 Check - Security List Ingress from 0.0.0.0/0 on ports 22, 3389 @@ -4247,17 +4250,21 @@ def __report_cis_analyze_tenancy_data(self): # Generating list of keys for key in self.__kms_keys: - if self.kms_key_time_max_datetime and self.kms_key_time_max_datetime >= datetime.datetime.strptime(key['currentKeyVersion_time_created'], self.__iso_time_format): - self.cis_foundations_benchmark_2_0['4.16']['Status'] = False - self.cis_foundations_benchmark_2_0['4.16']['Findings'].append( - key) - if self.kms_key_time_max_datetime is None: - self.cis_foundations_benchmark_2_0['4.16']['Status'] = False - self.cis_foundations_benchmark_2_0['4.16']['Findings'].append( - key) - - - # CIS Check 3.16 Total - Adding Key to total + try: + if self.kms_key_time_max_datetime and self.kms_key_time_max_datetime >= datetime.datetime.strptime(key['currentKeyVersion_time_created'], self.__iso_time_format): + self.cis_foundations_benchmark_2_0['4.16']['Status'] = False + self.cis_foundations_benchmark_2_0['4.16']['Findings'].append( + key) + if self.kms_key_time_max_datetime is None: + self.cis_foundations_benchmark_2_0['4.16']['Status'] = False + self.cis_foundations_benchmark_2_0['4.16']['Findings'].append( + key) + except: + self.cis_foundations_benchmark_2_0['4.16']['Status'] = False + self.cis_foundations_benchmark_2_0['4.16']['Findings'].append( + key) + + # CIS Check 4.16 Total - Adding Key to total self.cis_foundations_benchmark_2_0['4.16']['Total'].append(key) # CIS Check 4.17 - Object Storage with Logs @@ -4909,12 +4916,14 @@ def __report_generate_cis_report(self, level): # Generating Summary report CSV print_header("Writing CIS reports to CSV") summary_files = [] - summary_file_name = self.__print_to_csv_file( - self.__report_directory, "cis", "summary_report", summary_report) + summary_file_name = self.__print_to_csv_file("cis", "summary_report", summary_report) summary_files.append(summary_file_name) - summary_file_name = self.__report_generate_html_summary_report( - self.__report_directory, "cis", "html_summary_report", summary_report) + if self.__report_summary_json: + summary_file_name = self.__print_to_json_file("cis", "summary_report", summary_report) + summary_files.append(summary_file_name) + + summary_file_name = self.__report_generate_html_summary_report("cis", "html_summary_report", summary_report) summary_files.append(summary_file_name) # Outputing to a bucket if I have one @@ -4925,8 +4934,7 @@ def __report_generate_cis_report(self, level): for key, recommendation in self.cis_foundations_benchmark_2_0.items(): if recommendation['Level'] <= level: - report_file_name = self.__print_to_csv_file( - self.__report_directory, "cis", recommendation['section'] + "_" + recommendation['recommendation_#'], recommendation['Findings']) + report_file_name = self.__print_to_csv_file("cis", recommendation['section'] + "_" + recommendation['recommendation_#'], recommendation['Findings']) if report_file_name and self.__output_bucket: self.__os_copy_report_to_object_storage( self.__output_bucket, report_file_name) @@ -4934,11 +4942,11 @@ def __report_generate_cis_report(self, level): ########################################################################## # Generates an HTML report ########################################################################## - def __report_generate_html_summary_report(self, report_directory, header, file_subject, data): + def __report_generate_html_summary_report(self, header, file_subject, data): try: # Creating report directory - if not os.path.isdir(report_directory): - os.mkdir(report_directory) + if not os.path.isdir(self.__report_directory): + os.mkdir(self.__report_directory) except Exception as e: raise Exception("Error in creating report directory: " + str(e.args)) @@ -4948,11 +4956,10 @@ def __report_generate_html_summary_report(self, report_directory, header, file_s if len(data) == 0: return None - # get the file name of the CSV - + # get the file name of the HTML file_name = header + "_" + file_subject file_name = (file_name.replace(" ", "_")).replace(".", "-").replace("_-_", "_") + ".html" - file_path = os.path.join(report_directory, file_name) + file_path = os.path.join(self.__report_directory, f'{self.__report_prefix}{file_name}') # add report_datetimeto each dictionary result = [dict(item, extract_date=self.start_time_str) @@ -5238,8 +5245,7 @@ def __report_generate_obp_report(self): print_header("Writing Oracle Best Practices reports to CSV") - summary_report_file_name = self.__print_to_csv_file( - self.__report_directory, "obp", "OBP_Summary", obp_summary_report) + summary_report_file_name = self.__print_to_csv_file("obp", "OBP_Summary", obp_summary_report) if summary_report_file_name and self.__output_bucket: self.__os_copy_report_to_object_storage( @@ -5247,13 +5253,11 @@ def __report_generate_obp_report(self): # Printing Findings to CSV for key, value in self.obp_foundations_checks.items(): - report_file_name = self.__print_to_csv_file( - self.__report_directory, "obp", key + "_Findings", value['Findings']) + report_file_name = self.__print_to_csv_file("obp", key + "_Findings", value['Findings']) # Printing OBPs to CSV for key, value in self.obp_foundations_checks.items(): - report_file_name = self.__print_to_csv_file( - self.__report_directory, "obp", key + "_Best_Practices", value['OBP']) + report_file_name = self.__print_to_csv_file("obp", key + "_Best_Practices", value['OBP']) if report_file_name and self.__output_bucket: self.__os_copy_report_to_object_storage( @@ -5289,7 +5293,6 @@ def __collect_tenancy_data(self): self.__identity_read_users, self.__identity_read_tenancy_password_policy, self.__identity_read_dynamic_groups, - self.__audit_read_tenancy_audit_configuration, self.__identity_read_availability_domains, self.__identity_read_tag_defaults, self.__identity_read_tenancy_policies, @@ -5385,146 +5388,61 @@ def __report_generate_raw_data_output(self): # List to store output reports if copying to object storage is required list_report_file_names = [] - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_groups_and_membership", self.__groups_to_users) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_domains", self.__identity_domains) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_users", self.__users) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_policies", self.__policies) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_dynamic_groups", self.__dynamic_groups) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_tags", self.__tag_defaults) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "identity_compartments", self.__raw_compartment) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_security_groups", self.__network_security_groups) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_security_lists", self.__network_security_lists) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_subnets", self.__network_subnets) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "autonomous_databases", self.__autonomous_databases) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "analytics_instances", self.__analytics_instances) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "integration_instances", self.__integration_instances) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "event_rules", self.__event_rules) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "log_groups_and_logs", self.__logging_list) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "object_storage_buckets", self.__buckets) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "boot_volumes", self.__boot_volumes) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "block_volumes", self.__block_volumes) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "file_storage_system", self.__file_storage_system) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "keys_and_vaults", self.__kms_keys) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "ons_subscriptions", self.__subscriptions) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "budgets", self.__budgets) - list_report_file_names.append(report_file_name) - - # Converting a one to one dict to a list - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "service_connectors", list(self.__service_connectors.values())) - list_report_file_names.append(report_file_name) - - # Converting a dict that is one to a list to a flat list - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_fastconnects", (list(itertools.chain.from_iterable(self.__network_fastconnects.values())))) - list_report_file_names.append(report_file_name) - - # Converting a dict that is one to a list to a flat list - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_ipsec_connections", list(itertools.chain.from_iterable(self.__network_ipsec_connections.values()))) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_drgs", self.__raw_network_drgs) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "cloud_guard_target", list(self.__cloud_guard_targets.values())) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "regions", self.__raw_regions) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "network_drg_attachments", list(itertools.chain.from_iterable(self.__network_drg_attachments.values()))) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_csv_file( - self.__report_directory, "raw_data", "instances", self.__Instance) - list_report_file_names.append(report_file_name) - - report_file_name = self.__print_to_json_file( - self.__report_directory, "raw_data", "all_resources", self.__all_resources_json) - list_report_file_names.append(report_file_name) + raw_csv_files = { + "identity_groups_and_membership": self.__groups_to_users, + "identity_domains": self.__identity_domains, + "identity_users": self.__users, + "identity_policies": self.__policies, + "identity_dynamic_groups": self.__dynamic_groups, + "identity_tags": self.__tag_defaults, + "identity_compartments": self.__raw_compartment, + "network_security_groups": self.__network_security_groups, + "network_security_lists": self.__network_security_lists, + "network_subnets": self.__network_subnets, + "autonomous_databases": self.__autonomous_databases, + "analytics_instances": self.__analytics_instances, + "integration_instances": self.__integration_instances, + "event_rules": self.__event_rules, + "log_groups_and_logs": self.__logging_list, + "object_storage_buckets": self.__buckets, + "boot_volumes": self.__boot_volumes, + "block_volumes": self.__block_volumes, + "file_storage_system": self.__file_storage_system, + "keys_and_vaults": self.__kms_keys, + "ons_subscriptions": self.__subscriptions, + "budgets": self.__budgets, + "service_connectors": list(self.__service_connectors.values()), + "network_fastconnects": list(itertools.chain.from_iterable(self.__network_fastconnects.values())), + "network_ipsec_connections": list(itertools.chain.from_iterable(self.__network_ipsec_connections.values())), + "network_drgs": self.__raw_network_drgs, + "cloud_guard_target": list(self.__cloud_guard_targets.values()), + "regions": self.__raw_regions, + "network_drg_attachments": list(itertools.chain.from_iterable(self.__network_drg_attachments.values())), + "instances": self.__Instance + } + for key in raw_csv_files: + rfn = self.__print_to_csv_file('raw_data', key, raw_csv_files[key]) + list_report_file_names.append(rfn) - report_file_name = self.__print_to_json_file( - self.__report_directory, "raw_data", "oci_network_topologies", oci.util.to_dict(self.__network_topology_json)) - list_report_file_names.append(report_file_name) + raw_json_files = { + "all_resources": self.__all_resources_json, + "oci_network_topologies": oci.util.to_dict(self.__network_topology_json) + } + for key in raw_json_files: + rfn = self.__print_to_json_file('raw_data', key, raw_json_files[key]) + list_report_file_names.append(rfn) - report_file_name = self.__print_to_pkl_file( - self.__report_directory, "raw_data", "oci_network_topologies", self.__network_topology_json) - list_report_file_names.append(report_file_name) + raw_pkl_files = { + "oci_network_topologies": self.__network_topology_json + } + for key in raw_pkl_files: + rfn = self.__print_to_pkl_file('raw_data', key, raw_json_files[key]) + list_report_file_names.append(rfn) if self.__output_bucket: for raw_report in list_report_file_names: if raw_report: - self.__os_copy_report_to_object_storage( - self.__output_bucket, raw_report) + self.__os_copy_report_to_object_storage(self.__output_bucket, raw_report) ########################################################################## # Copy Report to Object Storage @@ -5547,12 +5465,12 @@ def __os_copy_report_to_object_storage(self, bucketname, filename): ########################################################################## # Print to CSV ########################################################################## - def __print_to_csv_file(self, report_directory, header, file_subject, data): + def __print_to_csv_file(self, header, file_subject, data): debug("__print_to_csv_file: " + header + "_" + file_subject) try: # Creating report directory - if not os.path.isdir(report_directory): - os.mkdir(report_directory) + if not os.path.isdir(self.__report_directory): + os.mkdir(self.__report_directory) except Exception as e: raise Exception( @@ -5567,7 +5485,7 @@ def __print_to_csv_file(self, report_directory, header, file_subject, data): file_name = header + "_" + file_subject file_name = (file_name.replace(" ", "_")).replace(".", "-").replace("_-_", "_") + ".csv" - file_path = os.path.join(report_directory, file_name) + file_path = os.path.join(self.__report_directory, f'{self.__report_prefix}{file_name}') # add report_datetimeto each dictionary result = [dict(item, extract_date=self.start_time_str) @@ -5614,11 +5532,11 @@ def __print_to_csv_file(self, report_directory, header, file_subject, data): ########################################################################## # Print to JSON ########################################################################## - def __print_to_json_file(self, report_directory, header, file_subject, data): + def __print_to_json_file(self, header, file_subject, data): try: # Creating report directory - if not os.path.isdir(report_directory): - os.mkdir(report_directory) + if not os.path.isdir(self.__report_directory): + os.mkdir(self.__report_directory) except Exception as e: raise Exception( @@ -5634,7 +5552,7 @@ def __print_to_json_file(self, report_directory, header, file_subject, data): file_name = header + "_" + file_subject file_name = (file_name.replace(" ", "_") ).replace(".", "-").replace("_-_","_") + ".json" - file_path = os.path.join(report_directory, file_name) + file_path = os.path.join(self.__report_directory, f'{self.__report_prefix}{file_name}') # Serializing JSON to string json_object = json.dumps(data, indent=4) @@ -5661,11 +5579,11 @@ def __print_to_json_file(self, report_directory, header, file_subject, data): ########################################################################## # Print to PKL ########################################################################## - def __print_to_pkl_file(self, report_directory, header, file_subject, data): + def __print_to_pkl_file(self, header, file_subject, data): try: # Creating report directory - if not os.path.isdir(report_directory): - os.mkdir(report_directory) + if not os.path.isdir(self.__report_directory): + os.mkdir(self.__report_directory) except Exception as e: raise Exception( @@ -5681,7 +5599,7 @@ def __print_to_pkl_file(self, report_directory, header, file_subject, data): file_name = header + "_" + file_subject file_name = (file_name.replace(" ", "_") ).replace(".", "-").replace("_-_","_") + ".pkl" - file_path = os.path.join(report_directory, file_name) + file_path = os.path.join(self.__report_directory, f'{self.__report_prefix}{file_name}') # Writing to json file with open(file_path, 'wb') as pkl_file: @@ -5722,8 +5640,7 @@ def generate_reports(self, level=2): self.__report_generate_raw_data_output() if self.__errors: - error_report = self.__print_to_csv_file( - self.__report_directory, "error", "report", self.__errors) + error_report = self.__print_to_csv_file("error", "report", self.__errors) if self.__output_bucket: if error_report: @@ -5795,8 +5712,7 @@ def create_signer(file_location, config_profile, is_instance_principals, is_dele # check if file exist if env_config_file is None or env_config_section is None: - print( - "*** OCI_CONFIG_FILE and OCI_CONFIG_PROFILE env variables not found, abort. ***") + print("*** OCI_CONFIG_FILE and OCI_CONFIG_PROFILE env variables not found, abort. ***") print("") raise SystemExit @@ -5908,39 +5824,43 @@ def execute_report(): # Get Command Line Parser parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=100, width=180)) parser.add_argument('-c', default="", dest='file_location', - help='OCI config file location') + help='OCI config file location.') parser.add_argument('-t', default="", dest='config_profile', - help='Config file section to use (tenancy profile) ') + help='Config file section to use (tenancy profile).') parser.add_argument('-p', default="", dest='proxy', - help='Set Proxy (i.e. www-proxy-server.com:80) ') + help='Set Proxy (i.e. www-proxy-server.com:80).') parser.add_argument('--output-to-bucket', default="", dest='output_bucket', - help='Set Output bucket name (i.e. my-reporting-bucket) ') + help='Set Output bucket name (i.e. my-reporting-bucket).') parser.add_argument('--report-directory', default=None, dest='report_directory', - help='Set Output report directory by default it is the current date (i.e. reports-date) ') + help='Set Output report directory by default it is the current date (i.e. reports-date).') + parser.add_argument('--report-prefix', default=None, dest='report_prefix', + help='Set Output report prefix to allow unique files for better baseline comparison.') + parser.add_argument('--report-summary-json', action='store_true', default=None, dest='report_summary_json', + help='Write summary report as JSON file, too.') parser.add_argument('--print-to-screen', default='True', dest='print_to_screen', - help='Set to False if you want to see only non-compliant findings (i.e. False) ') + help='Set to False if you want to see only non-compliant findings (i.e. False).') parser.add_argument('--level', default=2, dest='level', - help='CIS Recommendation Level options are: 1 or 2. Set to 2 by default ') + help='CIS Recommendation Level options are: 1 or 2. Set to 2 by default.') parser.add_argument('--regions', default="", dest='regions', - help='Regions to run the compliance checks on, by default it will run in all regions. Sample input: us-ashburn-1,ca-toronto-1,eu-frankfurt-1') + help='Regions to run the compliance checks on, by default it will run in all regions. Sample input: us-ashburn-1,ca-toronto-1,eu-frankfurt-1.') parser.add_argument('--raw', action='store_true', default=False, - help='Outputs all resource data into CSV files') + help='Outputs all resource data into CSV files.') parser.add_argument('--obp', action='store_true', default=False, - help='Checks for OCI best practices') + help='Checks for OCI best practices.') parser.add_argument('--all-resources', action='store_true', default=False, help='Uses Advanced Search Service to query all resources in the tenancy and outputs to a JSON. This also enables OCI Best Practice Checks (--obp) and All resource to csv (--raw) flags.') parser.add_argument('--redact_output', action='store_true', default=False, - help='Redacts OCIDs in output CSV and JSON files') + help='Redacts OCIDs in output CSV and JSON files.') parser.add_argument('-ip', action='store_true', default=False, - dest='is_instance_principals', help='Use Instance Principals for Authentication ') + dest='is_instance_principals', help='Use Instance Principals for Authentication.') parser.add_argument('-dt', action='store_true', default=False, - dest='is_delegation_token', help='Use Delegation Token for Authentication in Cloud Shell') + dest='is_delegation_token', help='Use Delegation Token for Authentication in Cloud Shell.') parser.add_argument('-st', action='store_true', default=False, - dest='is_security_token', help='Authenticate using Security Token') + dest='is_security_token', help='Authenticate using Security Token.') parser.add_argument('-v', action='store_true', default=False, dest='version', help='Show the version of the script and exit.') parser.add_argument('--debug', action='store_true', default=False, - dest='debug', help='Enables debugging messages. This feature is in beta') + dest='debug', help='Enables debugging messages. This feature is in beta.') cmd = parser.parse_args() if cmd.version: @@ -5949,16 +5869,17 @@ def execute_report(): config, signer = create_signer(cmd.file_location, cmd.config_profile, cmd.is_instance_principals, cmd.is_delegation_token, cmd.is_security_token) config['retry_strategy'] = oci.retry.DEFAULT_RETRY_STRATEGY - report = CIS_Report(config, signer, cmd.proxy, cmd.output_bucket, cmd.report_directory, cmd.print_to_screen, \ + report = CIS_Report(config, signer, cmd.proxy, cmd.output_bucket, cmd.report_directory, cmd.report_prefix, cmd.report_summary_json, cmd.print_to_screen, \ cmd.regions, cmd.raw, cmd.obp, cmd.redact_output, debug=cmd.debug, all_resources=cmd.all_resources) csv_report_directory = report.generate_reports(int(cmd.level)) try: if OUTPUT_TO_XLSX: - workbook = Workbook(csv_report_directory + '/Consolidated_Report.xlsx', {'in_memory': True}) - for csvfile in glob.glob(csv_report_directory + '/*.csv'): + report_prefix = f'{cmd.report_prefix}_' if cmd.report_prefix else '' + workbook = Workbook(f'{csv_report_directory}/{report_prefix}Consolidated_Report.xlsx', {'in_memory': True}) + for csvfile in glob.glob(f'{csv_report_directory}/{report_prefix}*.csv'): - worksheet_name = csvfile.split(os.path.sep)[-1].replace(".csv", "").replace("raw_data_", "raw_").replace("Findings", "fds").replace("Best_Practices", "bps") + worksheet_name = csvfile.split(os.path.sep)[-1].replace(report_prefix, "").replace(".csv", "").replace("raw_data_", "raw_").replace("Findings", "fds").replace("Best_Practices", "bps") if "Identity_and_Access_Management" in worksheet_name: worksheet_name = worksheet_name.replace("Identity_and_Access_Management", "IAM") @@ -5986,7 +5907,7 @@ def execute_report(): worksheet.write(r, c, col) workbook.close() except Exception as e: - print("**Failed to output to excel. Please use CSV files.**") + print("** Failed to output to excel. Please use CSV files. **") print(e)