> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OCI Network Security Groups Should Not Allow Public RDP Access

### More Info:

Network Security Groups should not allow RDP (port 3389) access from 0.0.0.0/0. Public RDP is one of the most exploited attack vectors for ransomware and unauthorized access.

### Risk Level

Critical

### Address

Compliance, Security

### Compliance Standards

* APRA CPS 234 (Australia)
* BSI C5 (Germany)
* Brazil LGPD
* CCPA / CPRA (California)
* CIS Critical Security Controls v8
* CMMC 2.0
* CSA Cloud Controls Matrix v4
* DPDPA
* Digital Operational Resilience Act (EU)
* ISO/IEC 27017
* ISO/IEC 27018
* ISO/IEC 27701
* KSA PDPL
* MAS Technology Risk Management (Singapore)
* MITRE ATT\&CK (Cloud)
* NIS2 Directive
* NIST
* NIST SP 800-171
* NYDFS 23 NYCRR 500
* SWIFT Customer Security Controls Framework
* Sarbanes-Oxley IT General Controls
* Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
* UK NCSC Cyber Assessment Framework

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        To stop public RDP access in OCI Network Security Groups (NSGs) using the OCI Console, you need to:

        * Find the NSG(s) with an ingress rule allowing TCP/3389 from 0.0.0.0/0 (or ::/0)
        * Remove or restrict that rule
        * (Optional) Use Monitoring/Alarms to detect if it reappears

        Below are the exact steps.

        ***

        ## 1. Identify NSGs with Public RDP Access

        1. Sign in to the **OCI Console**.
        2. At the top, set the correct **Region** and **Compartment** (or choose the parent compartment and enable “Include subcompartments” where applicable).
        3. In the left menu, go to:\
           **Networking** → **Network Security Groups**.
        4. For each NSG (or those linked to your Windows Compute instances):
           * Click the NSG name.
           * Go to the **Ingress Rules** tab.
           * Look for rules with:
             * **Stateless/Stateful**: Either
             * **Source Type**: CIDR
             * **Source CIDR**: `0.0.0.0/0` (and/or `::/0` for IPv6)
             * **IP Protocol**: `TCP`
             * **Destination Port Range**: `3389` (or a range including 3389)

        Any such rule is “public RDP access.”

        ***

        ## 2. Remediate the NSG Rule (Stop Public RDP)

        ### Option A – Delete the rule (recommended if not required)

        1. In the NSG **Ingress Rules** list, find the RDP rule (TCP 3389 from 0.0.0.0/0).
        2. Click the **Actions** menu (three dots) next to the rule.
        3. Click **Remove** (or **Delete** rule).
        4. Confirm deletion.

        This immediately blocks public RDP access via that NSG.

        ***

        ### Option B – Restrict the rule to specific IPs (if RDP needed)

        1. In the same **Ingress Rules** list, click the **Actions** menu next to the RDP rule.
        2. Click **Edit**.
        3. Change:
           * **Source Type**: `CIDR` (or `Service`/`NSG` if using internal access).
           * **Source CIDR**:
             * Replace `0.0.0.0/0` with your trusted IP/range, e.g.:
               * Single admin IP: `203.0.113.10/32`
               * Corporate egress range: e.g. `198.51.100.0/24`
           * Keep **IP Protocol** as `TCP`.
           * **Destination Port Range**: `3389` (or as needed).
        4. Click **Save changes**.

        Now only allowed IPs can reach RDP.

        ***

        ### Option C – Move security control from NSG to a bastion / VPN

        If you prefer no direct RDP from the internet:

        1. **Remove** public RDP rule from the NSG as in Option A.
        2. Use one of:
           * **OCI Bastion**:
             * Go to **Identity & Security → Bastion**.
             * Create a bastion in the same VCN.
             * Use **Session** types like RDP (via SSH tunnel or native where supported).
           * **Site-to-Site VPN / FastConnect**:
             * Ensure NSG ingress for RDP only from your on-prem CIDR, not the internet.
           * **Jump host** in a private subnet and only allow SSH/RDP from trusted IPs.

        ***

        ## 3. Confirm Instance-Level Exposure

        1. Go to **Compute → Instances**.
        2. Select the Windows instance.
        3. In the **Attached VNICs**, note:
           * NSG(s) attached to the VNIC.
           * Public IP presence:
             * If you remove the public IP, RDP is no longer directly exposed to the internet.
        4. If the instance does not need direct public RDP:
           * Under the VNIC, detach or unassign the **Public IP**, and access through Bastion/VPN instead.

        ***

        ## 4. (Optional) Add Monitoring / Alerting for Public RDP in Networking

        While OCI Monitoring is used primarily for metrics/alarms, for configuration-level findings (like public RDP) you typically use **Cloud Guard**:

        1. Go to **Identity & Security → Cloud Guard**.
        2. Ensure a **Cloud Guard Target** is configured for your tenancy/compartments.
        3. In **Detector Recipes**, confirm detectors for:
           * Public inbound access on sensitive ports (e.g., RDP – TCP/3389).
        4. Enable **Responder Recipes** or at least:
           * Configure **Notifications** via **Notifications → Topics** (e.g., Email/Slack) so any new public RDP rule triggers an alert.

        This doesn’t change the NSG by itself unless you enable and configure responders, but it helps detect regressions.

        ***

        ## Quick Checklist

        * [ ] Remove or edit NSG rules allowing TCP 3389 from `0.0.0.0/0` or `::/0`.
        * [ ] Prefer access via OCI Bastion, VPN, or trusted IP/CIDR only.
        * [ ] Confirm instances do not have unnecessary public IPs.
        * [ ] Use Cloud Guard to monitor for new public RDP exposures.

        If you tell me your current NSG rule details (screenshot or values), I can give you the exact before/after configuration.
      </Accordion>

      <Accordion title="Using CLI">
        Below is a simple, CLI‑only way to:

        1. Find NSG rules that allow public RDP (TCP/3389 from 0.0.0.0/0), and
        2. Remove or restrict those rules.

        Assumptions:

        * You have `oci` configured.
        * You know your `compartment-id` and `vcn-id` (if you want to scope by VCN).

        ***

        ## 1. List NSGs in the compartment (optionally by VCN)

        ```bash theme={null}
        export COMPARTMENT_OCID="<your_compartment_ocid>"

        oci network nsg list \
          --compartment-id "$COMPARTMENT_OCID" \
          --all
        ```

        Optionally filter to a specific VCN:

        ```bash theme={null}
        export VCN_OCID="<your_vcn_ocid>"

        oci network nsg list \
          --compartment-id "$COMPARTMENT_OCID" \
          --vcn-id "$VCN_OCID" \
          --all
        ```

        Copy the `id` of each NSG you want to check (e.g., `NSG_OCID`).

        ***

        ## 2. Identify NSG rules that allow public RDP

        For each NSG:

        ```bash theme={null}
        export NSG_OCID="<your_nsg_ocid>"

        oci network nsg get \
          --network-security-group-id "$NSG_OCID" \
          --query 'data."security-rules"' \
          --output table
        ```

        Look for **ingress** rules with:

        * `protocol = "6"` (TCP),
        * `isStateless = false` or true (either is fine),
        * `source = "0.0.0.0/0"`,
        * and `tcpOptions.destination-port-range.min = 3389` and `max = 3389`
          (or a range that includes 3389).

        Example offending rule (JSON shape):

        ```json theme={null}
        {
          "description": "Public RDP",
          "direction": "INGRESS",
          "isStateless": false,
          "protocol": "6",
          "source": "0.0.0.0/0",
          "tcpOptions": {
            "destinationPortRange": {
              "min": 3389,
              "max": 3389
            }
          }
        }
        ```

        Note the entire rule object; you will need to either remove it or edit it.

        ***

        ## 3. Export current rules as JSON (for safe editing)

        ```bash theme={null}
        oci network nsg get \
          --network-security-group-id "$NSG_OCID" \
          --query 'data."security-rules"' \
          --output json > nsg-rules.json
        ```

        Open `nsg-rules.json` and:

        * **Option A – Remove public RDP entirely:**\
          Delete the rule objects that allow TCP/3389 from `0.0.0.0/0`.

        * **Option B – Restrict to specific source(s):**\
          Change:
          ```json theme={null}
          "source": "0.0.0.0/0"
          ```
          to a more restrictive CIDR, for example:
          ```json theme={null}
          "source": "203.0.113.10/32"
          ```
          or your corporate IP range.

        Ensure the file remains a valid JSON array of rule objects.

        ***

        ## 4. Update the NSG with the corrected rules

        ```bash theme={null}
        oci network nsg update \
          --network-security-group-id "$NSG_OCID" \
          --security-rules file://nsg-rules.json
        ```

        Verify:

        ```bash theme={null}
        oci network nsg get \
          --network-security-group-id "$NSG_OCID" \
          --query 'data."security-rules"' \
          --output table
        ```

        Confirm that no `INGRESS` rule remains with:

        * `source = "0.0.0.0/0"`,
        * `protocol = "6"`,
        * and port 3389 (or ranges including 3389).

        ***

        ## 5. Optional: Scripted detection of public RDP rules

        Example using `jq` to show NSGs with public RDP:

        ```bash theme={null}
        oci network nsg list \
          --compartment-id "$COMPARTMENT_OCID" \
          --all \
          --query 'data[].id' \
          --output tsv | while read NSG_ID; do
            oci network nsg get \
              --network-security-group-id "$NSG_ID" \
              --query 'data."security-rules"' \
              --output json | \
            jq -r --arg nsg "$NSG_ID" '
              .[] | select(
                .direction=="INGRESS" and
                .protocol=="6" and
                .source=="0.0.0.0/0" and
                (.tcpOptions.destinationPortRange.min <= 3389 and
                 .tcpOptions.destinationPortRange.max >= 3389)
              ) | "NSG: \($nsg) has public RDP rule: \(.description // "no description")"
            '
        done
        ```

        Use the IDs reported by this script, then fix each NSG as per steps 3–4.

        ***

        If you want, share a sample `nsg-rules.json` (with sensitive data removed) and I can show exactly what to edit.
      </Accordion>

      <Accordion title="Using Python">
        Below is a concise, step‑by‑step way to detect and remediate public RDP access in OCI Network Security Groups (NSGs) using Python and the OCI SDK.

        ### 1. Prerequisites

        1. Install the OCI Python SDK:
           ```bash theme={null}
           pip install oci
           ```
        2. Configure your OCI CLI/SDK credentials (`~/.oci/config`):
           ```ini theme={null}
           [DEFAULT]
           user=ocid1.user.oc1..xxxx
           fingerprint=xx:xx:...
           key_file=/path/to/oci_api_key.pem
           tenancy=ocid1.tenancy.oc1..xxxx
           region=us-ashburn-1
           ```

        ### 2. Logic You Need

        You want to:

        1. List all NSGs in a compartment (or tenancy).
        2. For each NSG, list security rules.
        3. Identify rules that:
           * Are **ingress**,
           * Use **TCP**,
           * Allow **destination port 3389** (RDP),
           * Have source CIDR `0.0.0.0/0` or `::/0`.
        4. Remove or restrict those rules.

        ### 3. Python Script (Detect & Remediate)

        This example:

        * Scans one compartment.
        * Prints the violating rules.
        * Optionally removes them (toggle `DRY_RUN`).

        ```python theme={null}
        import oci

        # === CONFIG ===
        PROFILE = "DEFAULT"
        COMPARTMENT_ID = "ocid1.compartment.oc1..xxxx"  # root tenancy or specific compartment
        DRY_RUN = True  # set to False to actually remediate

        # RDP details
        RDP_PORT = 3389
        PUBLIC_CIDRS = ["0.0.0.0/0", "::/0"]


        def is_public_rdp_rule(rule):
            """
            Check if an NSG security rule is public RDP ingress.
            """
            if rule.direction != "INGRESS":
                return False

            # Only rules with protocol "6" (TCP) or "all"
            # In NSG rules protocol is a string: "6" -> TCP, "1" -> ICMP, "17" -> UDP, "all"
            if rule.protocol not in ("6", "all"):
                return False

            # Source must be CIDR block and public
            if not rule.source or rule.source not in PUBLIC_CIDRS:
                return False

            # For protocol "6" (TCP), check destination port range
            if rule.protocol == "6" and rule.tcp_options:
                dest_port_range = rule.tcp_options.destination_port_range
                if dest_port_range:
                    # Match if rule covers RDP port
                    if dest_port_range.min <= RDP_PORT <= dest_port_range.max:
                        return True
                return False

            # If protocol is "all" with public 0.0.0.0/0, it's even broader
            # You may or may not want to treat this as RDP exposure.
            # Here we *do* consider it bad for RDP.
            if rule.protocol == "all":
                return True

            return False


        def main():
            config = oci.config.from_file(profile_name=PROFILE)
            network_client = oci.core.VirtualNetworkClient(config)

            # 1. List all NSGs in the compartment
            nsgs = oci.pagination.list_call_get_all_results(
                network_client.list_network_security_groups,
                compartment_id=COMPARTMENT_ID
            ).data

            print(f"Found {len(nsgs)} NSGs in compartment {COMPARTMENT_ID}")

            for nsg in nsgs:
                print(f"\nChecking NSG: {nsg.display_name} ({nsg.id})")

                # 2. List rules for each NSG
                rules_response = network_client.list_network_security_group_security_rules(
                    network_security_group_id=nsg.id
                )
                rules = rules_response.data

                bad_rules = [r for r in rules if is_public_rdp_rule(r)]

                if not bad_rules:
                    print("  No public RDP rules found.")
                    continue

                print(f"  Found {len(bad_rules)} public RDP rule(s):")
                for r in bad_rules:
                    print(
                        f"    - Rule ID: {r.id}, direction: {r.direction}, "
                        f"protocol: {r.protocol}, source: {getattr(r, 'source', None)}, "
                        f"tcp_options: {r.tcp_options}"
                    )

                if DRY_RUN:
                    print("  DRY_RUN=True -> Not modifying rules.")
                    continue

                # 3. Remediate: remove the bad rules from the NSG
                #    You must send a *full* new rule set without the bad ones.
                new_rules = [r for r in rules if r.id not in {br.id for br in bad_rules}]

                # Convert existing rules (which are response models) to UpdateNetworkSecurityGroupSecurityRulesDetails
                # We re-use their attributes to preserve non-bad rules.
                from oci.core.models import (
                    UpdateNetworkSecurityGroupSecurityRulesDetails,
                    AddSecurityRuleDetails
                )

                update_rules_details = UpdateNetworkSecurityGroupSecurityRulesDetails(
                    security_rules=[
                        AddSecurityRuleDetails(
                            direction=r.direction,
                            protocol=r.protocol,
                            description=r.description,
                            is_stateless=r.is_stateless,
                            source=getattr(r, "source", None),
                            source_type=getattr(r, "source_type", None),
                            destination=getattr(r, "destination", None),
                            destination_type=getattr(r, "destination_type", None),
                            tcp_options=r.tcp_options,
                            udp_options=r.udp_options,
                            icmp_options=r.icmp_options
                        )
                        for r in new_rules
                    ]
                )

                print("  Updating NSG to remove public RDP rules...")
                network_client.update_network_security_group_security_rules(
                    network_security_group_id=nsg.id,
                    update_network_security_group_security_rules_details=update_rules_details
                )
                print("  Update complete.")

            print("\nScan/remediation complete.")


        if __name__ == "__main__":
            main()
        ```

        ### 4. How to Use This for Monitoring

        * Keep `DRY_RUN = True` and run the script on a schedule (e.g., cron / OCI Functions + Events) to only detect and report.
        * Send findings to:
          * Logging (stdout + OCI Logging),
          * Email (OCI Notifications),
          * Ticketing system (via webhook/HTTP calls).

        ### 5. To Auto‑Remediate

        * Set `DRY_RUN = False` in automation context.
        * Optionally, instead of removing rules, modify them to a restricted CIDR (e.g., corporate IP range) by adjusting the `AddSecurityRuleDetails` for matching rules rather than removing them.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        resource "oci_core_network_security_group" "example_nsg" {
          # Existing NSG
          compartment_id = VAR_COMPARTMENT_OCID          # replace with your compartment OCID variable or value
          vcn_id         = VAR_VCN_OCID                  # replace with your VCN OCID variable or value
          display_name   = "example-nsg"
        }

        # PRIOR MISCONFIGURATION (for reference only, remove from your code if present):
        # resource "oci_core_network_security_group_security_rule" "public_rdp" {
        #   network_security_group_id = oci_core_network_security_group.example_nsg.id
        #   direction                 = "INGRESS"
        #   protocol                  = "6" # TCP
        #
        #   tcp_options {
        #     destination_port_range {
        #       min = 3389
        #       max = 3389
        #     }
        #   }
        #
        #   source      = "0.0.0.0/0"  # <- PUBLIC, REMOVE/CHANGE THIS
        #   source_type = "CIDR_BLOCK"
        # }

        # REMEDIATED RULE: restrict RDP so it is not open to 0.0.0.0/0
        resource "oci_core_network_security_group_security_rule" "restricted_rdp" {
          network_security_group_id = oci_core_network_security_group.example_nsg.id
          direction                 = "INGRESS"
          protocol                  = "6" # TCP

          tcp_options {
            destination_port_range {
              min = 3389
              max = 3389
            }
          }

          # Use a restricted source instead of 0.0.0.0/0.
          # Option 1: Limit to a trusted CIDR range
          source      = "TRUSTED_ADMIN_CIDR"   # replace with an allowed admin subnet, e.g. "203.0.113.0/24"
          source_type = "CIDR_BLOCK"

          # Option 2 (alternative): reference another NSG instead of a CIDR:
          # source      = oci_core_network_security_group.admin_nsg.id
          # source_type = "NETWORK_SECURITY_GROUP"
        }
        ```

        If you are replacing an existing `oci_core_network_security_group_security_rule` that allowed `0.0.0.0/0` on port 3389, Terraform will show that rule being destroyed and a new, more restrictive rule being created; this is a replacement of the rule resource (not the NSG itself) and will immediately stop public RDP once applied.

        For verification, `terraform plan` should show that any rule with `source = "0.0.0.0/0"` and `destination_port_range` 3389 is removed or updated so that:

        * no ingress rule on TCP/3389 has `source = "0.0.0.0/0"`, and
        * the new/updated rule uses a restricted CIDR or NSG source as in the snippet above.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
