Repository: getmoto/moto (python)

Issue:
Enhancement: Request: Add support for RDS Proxy Target Groups
# Request
It would be great if Moto added support for db proxy target groups. According to the [RDS feature list](https://docs.getmoto.org/en/latest/docs/services/rds.html) it is missing both the describe and modify action: `[ ] describe_db_proxy_target_groups` & `[] modify_db_proxy_target_group`. Using the following code, Terraform eventually times out after a long while and gives the following error:

```shell
# Error
│ Error: updating RDS DB Proxy Default Target Group (testdb): operation error RDS: ModifyDBProxyTargetGroup, exceeded maximum number of attempts, 25, https response error StatusCode: 500, RequestID: , error while deserializing xml error response: XML syntax error on line 2: unquoted or missing attribute value in element
│ 
│   with aws_db_proxy_default_target_group.main,
│   on rds.tf line 30, in resource "aws_db_proxy_default_target_group" "main":
│   30: resource "aws_db_proxy_default_target_group" "main" {
```

Thank you!

# Reproduction
providers.tf
```hcl
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

provider "aws" {
  region                      = "us-east-1"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    ec2            = "http://localhost:5000"
    iam            = "http://localhost:5000"
    rds            = "http://localhost:5000"
    secretsmanager = "http://localhost:5000"
  }

  access_key = "my-access-key"
  secret_key = "my-secret-key"
}
```
rds-prereqs.tf
```hcl
data "aws_vpc" "default" {
  default = true
}

data "aws_subnets" "all" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

resource "random_string" "this" {
  length  = 4
  special = false
  upper   = false
}

resource "aws_secretsmanager_secret" "test" {
  name = "rds/test/test_admin"
}

resource "aws_secretsmanager_secret_version" "test" {
  secret_id = aws_secretsmanager_secret.test.id
  secret_string = jsonencode({
    username = "test_admin"
    password = "${random_string.this.result}"
  })
}

data "aws_iam_policy_document" "rds_trust_policy" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["rds.amazonaws.com"]
    }

    actions = [
      "sts:AssumeRole",
    ]
  }
}

resource "aws_iam_role" "proxy" {
  assume_role_policy = data.aws_iam_policy_document.rds_trust_policy.json
  name               = "rds-proxy-${aws_db_instance.test.db_name}"
}

```
rds.tf
```hcl
resource "aws_db_instance" "test" {
  allocated_storage   = 10
  db_name             = "testdb"
  engine              = "postgres"
  engine_version      = "15"
  instance_class      = "db.t3.micro"
  username            = "test"
  password            = random_string.this.result
  skip_final_snapshot = true
}

resource "aws_db_proxy" "main" {
  name                   = aws_db_instance.test.db_name
  debug_logging          = false
  engine_family          = "POSTGRESQL"
  idle_client_timeout    = 1800
  require_tls            = true
  role_arn               = aws_iam_role.proxy.arn
  vpc_security_group_ids = []
  vpc_subnet_ids         = data.aws_subnets.all.ids

  auth {
    auth_scheme = "SECRETS"
    description = "Secrets through IAM Authentication"
    iam_auth    = "REQUIRED"
    secret_arn  = aws_secretsmanager_secret.test.arn
  }
}

resource "aws_db_proxy_default_target_group" "main" {
  db_proxy_name = aws_db_proxy.main.name

  connection_pool_config {
    connection_borrow_timeout    = 120
    max_connections_percent      = 100
    max_idle_connections_percent = 50
    session_pinning_filters      = ["EXCLUDE_VARIABLE_SETS"]
  }
}

resource "aws_db_proxy_target" "main" {
  db_instance_identifier = aws_db_instance.test.identifier
  db_proxy_name          = aws_db_proxy.main.name
  target_group_name      = aws_db_proxy_default_target_group.main.name
}
```
# Expectation
It would be exceptional if I could create an `aws_db_proxy_default_target_group` and `aws_db_proxy_target` with Terraform using Moto to mock AWS for testing purposes for code utilizing these resources. Thanks again!

# Current Setup
A very basic `docker-compose.yml` file:
```yaml
  moto-server:
    image: motoserver/moto:latest
    ports:
      - 5000:5000
    environment:
      - MOTO_IAM_LOAD_MANAGED_POLICIES=true
```

Pull request:
RDS: Proxy Target Groups
Fixes #8159 

Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index ffdc5862e..f80ef52af 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -6460,7 +6460,7 @@
 
 ## rds
 <details>
-<summary>38% implemented</summary>
+<summary>42% implemented</summary>
 
 - [ ] add_role_to_db_cluster
 - [ ] add_role_to_db_instance
@@ -6505,7 +6505,7 @@
 - [X] delete_db_instance
 - [ ] delete_db_instance_automated_backup
 - [X] delete_db_parameter_group
-- [ ] delete_db_proxy
+- [X] delete_db_proxy
 - [ ] delete_db_proxy_endpoint
 - [ ] delete_db_security_group
 - [ ] delete_db_shard_group
@@ -6516,7 +6516,7 @@
 - [ ] delete_integration
 - [X] delete_option_group
 - [ ] delete_tenant_database
-- [ ] deregister_db_proxy_targets
+- [X] deregister_db_proxy_targets
 - [ ] describe_account_attributes
 - [ ] describe_blue_green_deployments
 - [ ] describe_certificates
@@ -6536,8 +6536,8 @@
 - [ ] describe_db_parameters
 - [X] describe_db_proxies
 - [ ] describe_db_proxy_endpoints
-- [ ] describe_db_proxy_target_groups
-- [ ] describe_db_proxy_targets
+- [X] describe_db_proxy_target_groups
+- [X] describe_db_proxy_targets
 - [ ] describe_db_recommendations
 - [ ] describe_db_security_groups
 - [ ] describe_db_shard_groups
@@ -6580,7 +6580,7 @@
 - [X] modify_db_parameter_group
 - [ ] modify_db_proxy
 - [ ] modify_db_proxy_endpoint
-- [ ] modify_db_proxy_target_group
+- [X] modify_db_proxy_target_group
 - [ ] modify_db_recommendation
 - [ ] modify_db_shard_group
 - [ ] modify_db_snapshot
@@ -6597,7 +6597,7 @@
 - [ ] reboot_db_cluster
 - [X] reboot_db_instance
 - [ ] reboot_db_shard_group
-- [ ] register_db_proxy_targets
+- [X] register_db_proxy_targets
 - [X] remove_from_global_cluster
 - [ ] remove_role_from_db_cluster
 - [ ] remove_role_from_db_instance
diff --git a/docs/docs/services/rds.rst b/docs/docs/services/rds.rst
index bc0f6ecc4..f2a8bab77 100644
--- a/docs/docs/services/rds.rst
+++ b/docs/docs/services/rds.rst
@@ -57,7 +57,7 @@ rds
 - [X] delete_db_instance
 - [ ] delete_db_instance_automated_backup
 - [X] delete_db_parameter_group
-- [ ] delete_db_proxy
+- [X] delete_db_proxy
 - [ ] delete_db_proxy_endpoint
 - [ ] delete_db_security_group
 - [ ] delete_db_shard_group
@@ -68,7 +68,7 @@ rds
 - [ ] delete_integration
 - [X] delete_option_group
 - [ ] delete_tenant_database
-- [ ] deregister_db_proxy_targets
+- [X] deregister_db_proxy_targets
 - [ ] describe_account_attributes
 - [ ] describe_blue_green_deployments
 - [ ] describe_certificates
@@ -92,8 +92,8 @@ rds
         
 
 - [ ] describe_db_proxy_endpoints
-- [ ] describe_db_proxy_target_groups
-- [ ] describe_db_proxy_targets
+- [X] describe_db_proxy_target_groups
+- [X] describe_db_proxy_targets
 - [ ] describe_db_recommendations
 - [ ] describe_db_security_groups
 - [ ] describe_db_shard_groups
@@ -140,7 +140,7 @@ rds
 - [X] modify_db_parameter_group
 - [ ] modify_db_proxy
 - [ ] modify_db_proxy_endpoint
-- [ ] modify_db_proxy_target_group
+- [X] modify_db_proxy_target_group
 - [ ] modify_db_recommendation
 - [ ] modify_db_shard_group
 - [ ] modify_db_snapshot
@@ -157,7 +157,7 @@ rds
 - [ ] reboot_db_cluster
 - [X] reboot_db_instance
 - [ ] reboot_db_shard_group
-- [ ] register_db_proxy_targets
+- [X] register_db_proxy_targets
 - [X] remove_from_global_cluster
 - [ ] remove_role_from_db_cluster
 - [ ] remove_role_from_db_instance
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 2a768a91a..51229c102 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -1,4 +1,5 @@
 import copy
+import math
 import os
 import re
 import string
@@ -121,6 +122,71 @@ class RDSBaseModel(TaggingMixin, BaseModel):
         return f"arn:{self.partition}:rds:{self.region}:{self.account_id}:{self.resource_type}:{self.name}"
 
 
+class ProxyTarget(RDSBaseModel):
+    resource_type = "proxy-target"
+
+    def __init__(
+        self,
+        backend: "RDSBackend",
+        resource_id: str,
+        endpoint: Optional[str],
+        type: str,
+    ):
+        super().__init__(backend)
+        self.endpoint = endpoint
+        self.rds_resource_id = resource_id
+        self.type = type
+        self.role = ""
+
+
+class ProxyTargetGroup(RDSBaseModel):
+    resource_type = "target-group"
+
+    def __init__(
+        self,
+        backend: "RDSBackend",
+        name: str,
+        proxy_name: str,
+    ):
+        super().__init__(backend)
+        self._name = f"prx-tg-{random.get_random_string(length=17, lower_case=True)}"
+        self.group_name = name
+        self.proxy_name = proxy_name
+        self.targets: List[ProxyTarget] = []
+
+        self.max_connections = 100
+        self.max_idle_connections = 50
+        self.borrow_timeout = 120
+        self.session_pinning_filters: List[str] = []
+
+        self.created_date = iso_8601_datetime_with_milliseconds()
+        self.updated_date = iso_8601_datetime_with_milliseconds()
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    def to_xml(self) -> str:
+        template = Template("""<DBProxyName>{{ group.proxy_name }}</DBProxyName>
+      <TargetGroupName>{{ group.group_name }}</TargetGroupName>
+      <TargetGroupArn>{{ group.arn }}</TargetGroupArn>
+      <IsDefault>true</IsDefault>
+      <Status>available</Status>
+      <ConnectionPoolConfig>
+        <MaxConnectionsPercent>{{ group.max_connections }}</MaxConnectionsPercent>
+        <MaxIdleConnectionsPercent>{{ group.max_idle_connections }}</MaxIdleConnectionsPercent>
+        <ConnectionBorrowTimeout>{{ group.borrow_timeout }}</ConnectionBorrowTimeout>
+        <SessionPinningFilters>
+        {% for filter in group.session_pinning_filters %}
+        <member>{{ filter }}</member>
+        {% endfor %}
+        </SessionPinningFilters>
+      </ConnectionPoolConfig>
+      <CreatedDate>{{ group.created_date }}</CreatedDate>
+      <UpdatedDate>{{ group.updated_date }}</UpdatedDate>""")
+        return template.render(group=self)
+
+
 class GlobalCluster(RDSBaseModel):
     resource_type = "global-cluster"
 
@@ -729,7 +795,7 @@ class DBInstance(CloudFormationModel, RDSBaseModel):
         self.instance_create_time = iso_8601_datetime_with_milliseconds()
         self.publicly_accessible = kwargs.get("publicly_accessible")
         if self.publicly_accessible is None:
-            self.publicly_accessible = True
+            self.publicly_accessible = False
         self.copy_tags_to_snapshot = kwargs.get("copy_tags_to_snapshot")
         if self.copy_tags_to_snapshot is None:
             self.copy_tags_to_snapshot = False
@@ -750,6 +816,11 @@ class DBInstance(CloudFormationModel, RDSBaseModel):
             ].describe_db_subnet_groups(self.db_subnet_group_name)[0]
         self.security_groups = kwargs.get("security_groups", [])
         self.vpc_security_group_ids = kwargs.get("vpc_security_group_ids", [])
+        if not self.vpc_security_group_ids:
+            ec2_backend = ec2_backends[self.account_id][self.region]
+            default_vpc = ec2_backend.default_vpc
+            default_sg = ec2_backend.get_default_security_group(default_vpc.id)
+            self.vpc_security_group_ids.append(default_sg.id)  # type: ignore
         self.preferred_maintenance_window = kwargs.get("preferred_maintenance_window")
         self.preferred_backup_window = kwargs.get("preferred_backup_window")
         msg = valid_preferred_maintenance_window(
@@ -1550,7 +1621,7 @@ class DBProxy(RDSBaseModel):
         self.auth = auth
         self.role_arn = role_arn
         self.vpc_subnet_ids = vpc_subnet_ids
-        self.vpc_security_group_ids = vpc_security_group_ids
+        self.vpc_security_group_ids = vpc_security_group_ids or []
         self.require_tls = require_tls
         if idle_client_timeout is None:
             self.idle_client_timeout = 1800
@@ -1577,6 +1648,9 @@ class DBProxy(RDSBaseModel):
             vpcs.append(subnet.vpc_id)
             if subnet.vpc_id != vpcs[0]:
                 raise InvalidSubnet(subnet_identifier=subnet.id)
+        if not self.vpc_security_group_ids:
+            default_sg = ec2_backend.get_default_security_group(vpcs[0])
+            self.vpc_security_group_ids.append(default_sg.id)  # type: ignore
 
         self.vpc_id = ec2_backend.describe_subnets(subnet_ids=[self.vpc_subnet_ids[0]])[
             0
@@ -1587,18 +1661,30 @@ class DBProxy(RDSBaseModel):
         )
         self.endpoint = f"{self.db_proxy_name}.db-proxy-{self.url_identifier}.{self.region}.rds.amazonaws.com"
 
+        self.proxy_target_groups = {
+            "default": ProxyTargetGroup(
+                backend=self.backend, name="default", proxy_name=db_proxy_name
+            )
+        }
+
+        self.unique_id = f"prx-{random.get_random_string(17, lower_case=True)}"
+
     @property
     def name(self) -> str:
         return self.db_proxy_name
 
+    @property
+    def arn(self) -> str:
+        return f"arn:{self.partition}:rds:{self.region}:{self.account_id}:{self.resource_type}:{self.unique_id}"
+
     def to_xml(self) -> str:
         template = Template(
             """
                 <RequireTLS>{{ dbproxy.require_tls }}</RequireTLS>
                 <VpcSecurityGroupIds>
-                {% if dbproxy.VpcSecurityGroupIds %}
-                  {% for vpcsecuritygroupid in dbproxy.VpcSecurityGroupIds %}
-                    <member>{{ vpcsecuritygroupid }}</member>
+                {% if dbproxy.vpc_security_group_ids %}
+                  {% for sg in dbproxy.vpc_security_group_ids %}
+                    <member>{{ sg }}</member>
                   {% endfor %}
                 {% endif %}
                 </VpcSecurityGroupIds>
@@ -2558,6 +2644,13 @@ class RDSBackend(BaseBackend):
             if resource_type == getattr(resource_class, "resource_type", ""):
                 if resource_name in resources:  # type: ignore
                     return resources[resource_name]  # type: ignore
+        # The resource_name is the last part of the ARN
+        # Usually that's the name - but for DBProxies, the last part of the ARN is a random identifier
+        # So we can't just use the dict-keys - we have to manually check the ARN
+        if resource_type == "db-proxy":
+            for resource in self.db_proxies.values():
+                if resource.arn.endswith(resource_name):
+                    return resource
 
     def list_tags_for_resource(self, arn: str) -> List[Dict[str, str]]:
         if self.arn_regex.match(arn):
@@ -2869,6 +2962,86 @@ class RDSBackend(BaseBackend):
             raise DBProxyNotFoundFault(db_proxy_name)
         return db_proxies
 
+    def deregister_db_proxy_targets(
+        self,
+        db_proxy_name: str,
+        target_group_name: str,
+        db_cluster_identifiers: List[str],
+        db_instance_identifiers: List[str],
+    ) -> None:
+        db_proxy = self.db_proxies[db_proxy_name]
+        target_group = db_proxy.proxy_target_groups[target_group_name or "default"]
+        target_group.targets = [
+            t
+            for t in target_group.targets
+            if t.rds_resource_id not in db_cluster_identifiers
+            and t.rds_resource_id not in db_instance_identifiers
+        ]
+
+    def register_db_proxy_targets(
+        self,
+        db_proxy_name: str,
+        target_group_name: str,
+        db_cluster_identifiers: List[str],
+        db_instance_identifiers: List[str],
+    ) -> List[ProxyTarget]:
+        db_proxy = self.db_proxies[db_proxy_name]
+        target_group = db_proxy.proxy_target_groups[target_group_name or "default"]
+        new_targets = []
+        for cluster_id in db_cluster_identifiers:
+            cluster = self.clusters[cluster_id]
+            target = ProxyTarget(
+                backend=self,
+                resource_id=cluster_id,
+                endpoint=cluster.endpoint,
+                type="TRACKED_CLUSTER",
+            )
+            new_targets.append(target)
+        for instance_id in db_instance_identifiers:
+            target = ProxyTarget(
+                backend=self,
+                resource_id=instance_id,
+                endpoint=None,
+                type="RDS_INSTANCE",
+            )
+            new_targets.append(target)
+        target_group.targets.extend(new_targets)
+        return new_targets
+
+    def delete_db_proxy(self, proxy_name: str) -> DBProxy:
+        return self.db_proxies.pop(proxy_name)
+
+    def describe_db_proxy_targets(self, proxy_name: str) -> List[ProxyTarget]:
+        proxy = self.db_proxies[proxy_name]
+        target_group = proxy.proxy_target_groups["default"]
+        return target_group.targets
+
+    def describe_db_proxy_target_groups(
+        self, proxy_name: str
+    ) -> List[ProxyTargetGroup]:
+        proxy = self.db_proxies[proxy_name]
+        return list(proxy.proxy_target_groups.values())
+
+    def modify_db_proxy_target_group(
+        self, proxy_name: str, config: Dict[str, Any]
+    ) -> ProxyTargetGroup:
+        proxy = self.db_proxies[proxy_name]
+        target_group = proxy.proxy_target_groups["default"]
+        if max_connections := config.get("MaxConnectionsPercent"):
+            target_group.max_connections = max_connections
+        if max_idle := config.get("MaxIdleConnectionsPercent"):
+            target_group.max_idle_connections = max_idle
+        else:
+            target_group.max_idle_connections = math.floor(
+                int(target_group.max_connections) / 2
+            )
+        target_group.borrow_timeout = config.get(
+            "ConnectionBorrowTimeout", target_group.borrow_timeout
+        )
+        if "SessionPinningFilters" in config:
+            target_group.session_pinning_filters = config["SessionPinningFilters"]
+        return target_group
+
 
 class OptionGroup(RDSBaseModel):
     resource_type = "og"
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index 8be586ff0..7b8911369 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -24,7 +24,7 @@ class RDSResponse(BaseResponse):
             "allocated_storage": self._get_int_param("AllocatedStorage"),
             "availability_zone": self._get_param("AvailabilityZone"),
             "backup_retention_period": self._get_param("BackupRetentionPeriod"),
-            "copy_tags_to_snapshot": self._get_param("CopyTagsToSnapshot"),
+            "copy_tags_to_snapshot": self._get_bool_param("CopyTagsToSnapshot"),
             "db_instance_class": self._get_param("DBInstanceClass"),
             "db_cluster_identifier": self._get_param("DBClusterIdentifier"),
             "db_instance_identifier": self._get_param("DBInstanceIdentifier"),
@@ -57,7 +57,7 @@ class RDSResponse(BaseResponse):
             "preferred_maintenance_window": self._get_param(
                 "PreferredMaintenanceWindow", "wed:06:38-wed:07:08"
             ).lower(),
-            "publicly_accessible": self._get_param("PubliclyAccessible"),
+            "publicly_accessible": self._get_bool_param("PubliclyAccessible"),
             "security_groups": self._get_multi_param(
                 "DBSecurityGroups.DBSecurityGroupName"
             ),
@@ -79,7 +79,7 @@ class RDSResponse(BaseResponse):
             "availability_zone": self._get_param("AvailabilityZone"),
             "backup_retention_period": self._get_param("BackupRetentionPeriod"),
             "backtrack_window": self._get_param("BacktrackWindow"),
-            "copy_tags_to_snapshot": self._get_param("CopyTagsToSnapshot"),
+            "copy_tags_to_snapshot": self._get_bool_param("CopyTagsToSnapshot"),
             "db_instance_class": self._get_param("DBInstanceClass"),
             "db_cluster_identifier": self._get_param("DBClusterIdentifier"),
             "new_db_cluster_identifier": self._get_param("NewDBClusterIdentifier"),
@@ -112,7 +112,7 @@ class RDSResponse(BaseResponse):
             "preferred_maintenance_window": self._get_param(
                 "PreferredMaintenanceWindow"
             ),
-            "publicly_accessible": self._get_param("PubliclyAccessible"),
+            "publicly_accessible": self._get_bool_param("PubliclyAccessible"),
             "security_groups": self._get_multi_param(
                 "DBSecurityGroups.DBSecurityGroupName"
             ),
@@ -137,7 +137,7 @@ class RDSResponse(BaseResponse):
             "iops": self._get_int_param("Iops"),
             # OptionGroupName
             "port": self._get_param("Port"),
-            "publicly_accessible": self._get_param("PubliclyAccessible"),
+            "publicly_accessible": self._get_bool_param("PubliclyAccessible"),
             "source_db_identifier": self._get_param("SourceDBInstanceIdentifier"),
             "storage_type": self._get_param("StorageType"),
         }
@@ -869,6 +869,63 @@ class RDSResponse(BaseResponse):
         template = self.response_template(CREATE_DB_PROXY_TEMPLATE)
         return template.render(dbproxy=db_proxy)
 
+    def register_db_proxy_targets(self) -> str:
+        db_proxy_name = self._get_param("DBProxyName")
+        target_group_name = self._get_param("TargetGroupName")
+        db_cluster_identifiers = self._get_params().get("DBClusterIdentifiers", [])
+        db_instance_identifiers = self._get_params().get("DBInstanceIdentifiers", [])
+        targets = self.backend.register_db_proxy_targets(
+            db_proxy_name=db_proxy_name,
+            target_group_name=target_group_name,
+            db_cluster_identifiers=db_cluster_identifiers,
+            db_instance_identifiers=db_instance_identifiers,
+        )
+
+        template = self.response_template(REGISTER_DB_PROXY_TARGET)
+        return template.render(targets=targets)
+
+    def deregister_db_proxy_targets(self) -> str:
+        db_proxy_name = self._get_param("DBProxyName")
+        target_group_name = self._get_param("TargetGroupName")
+        db_cluster_identifiers = self._get_params().get("DBClusterIdentifiers", [])
+        db_instance_identifiers = self._get_params().get("DBInstanceIdentifiers", [])
+        self.backend.deregister_db_proxy_targets(
+            db_proxy_name=db_proxy_name,
+            target_group_name=target_group_name,
+            db_cluster_identifiers=db_cluster_identifiers,
+            db_instance_identifiers=db_instance_identifiers,
+        )
+
+        template = self.response_template(DEREGISTER_DB_PROXY_TARGET)
+        return template.render()
+
+    def describe_db_proxy_targets(self) -> str:
+        proxy_name = self._get_param("DBProxyName")
+        targets = self.backend.describe_db_proxy_targets(proxy_name=proxy_name)
+        template = self.response_template(DESCRIBE_DB_PROXY_TARGETS)
+        return template.render(targets=targets)
+
+    def delete_db_proxy(self) -> str:
+        proxy_name = self._get_param("DBProxyName")
+        proxy = self.backend.delete_db_proxy(proxy_name=proxy_name)
+        template = self.response_template(DELETE_DB_PROXY_TEMPLATE)
+        return template.render(dbproxy=proxy)
+
+    def describe_db_proxy_target_groups(self) -> str:
+        proxy_name = self._get_param("DBProxyName")
+        groups = self.backend.describe_db_proxy_target_groups(proxy_name=proxy_name)
+        template = self.response_template(DESCRIBE_DB_PROXY_TARGET_GROUPS)
+        return template.render(groups=groups)
+
+    def modify_db_proxy_target_group(self) -> str:
+        proxy_name = self._get_param("DBProxyName")
+        config = self._get_params().get("ConnectionPoolConfig", {})
+        group = self.backend.modify_db_proxy_target_group(
+            proxy_name=proxy_name, config=config
+        )
+        template = self.response_template(MODIFY_DB_PROXY_TARGET_GROUP)
+        return template.render(group=group)
+
     def _paginate(self, resources: List[Any]) -> Tuple[List[Any], Optional[str]]:
         from moto.rds.exceptions import InvalidParameterValue
 
@@ -1717,3 +1774,79 @@ REMOVE_FROM_GLOBAL_CLUSTER_TEMPLATE = """<RemoveFromGlobalClusterResponse xmlns=
   {% endif %}
   </RemoveFromGlobalClusterResult>
 </RemoveFromGlobalClusterResponse>"""
+
+
+DEREGISTER_DB_PROXY_TARGET = """<DeregisterDBProxyTargetsResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+<DeregisterDBProxyTargetsResult>
+</DeregisterDBProxyTargetsResult>
+</DeregisterDBProxyTargetsResponse>"""
+
+
+REGISTER_DB_PROXY_TARGET = """<RegisterDBProxyTargetsResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+<RegisterDBProxyTargetsResult>
+<DBProxyTargets>
+  {% for target in targets %}
+  <member>
+    <RdsResourceId>{{ target.rds_resource_id }}</RdsResourceId>
+    <Port>5432</Port>
+    <Type>{{ target.type }}</Type>
+    <TargetHealth>
+        <State>REGISTERING</State>
+    </TargetHealth>
+    {% if target.endpoint %}<Endpoint>{{ target.endpoint }}</Endpoint>{% endif %}
+  </member>
+  {% endfor %}
+</DBProxyTargets>
+</RegisterDBProxyTargetsResult>
+</RegisterDBProxyTargetsResponse>"""
+
+
+DESCRIBE_DB_PROXY_TARGETS = """<DescribeDBProxyTargetsResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+<DescribeDBProxyTargetsResult>
+  <Targets>
+    {% for target in targets %}
+      <member>
+        <RdsResourceId>{{ target.rds_resource_id }}</RdsResourceId>
+        <Port>5432</Port>
+        <Type>{{ target.type }}</Type>
+        <TargetHealth>
+            <State>AVAILABLE</State>
+        </TargetHealth>
+        {% if target.endpoint %}<Endpoint>{{ target.endpoint }}</Endpoint>{% endif %}
+      </member>
+    {% endfor %}
+  </Targets>
+</DescribeDBProxyTargetsResult>
+</DescribeDBProxyTargetsResponse>
+"""
+
+
+DELETE_DB_PROXY_TEMPLATE = """<DeleteDBProxyResponse>
+<DeleteDBProxyResult>
+  <DBProxy>
+    {{ dbproxy.to_xml() }}
+  </DBProxy>
+</DeleteDBProxyResult>
+</DeleteDBProxyResponse>"""
+
+
+DESCRIBE_DB_PROXY_TARGET_GROUPS = """<DescribeDBProxyTargetGroupsResponse>
+<DescribeDBProxyTargetGroupsResult>
+  <TargetGroups>
+  {% for group in groups %}
+    <member>
+      {{ group.to_xml() }}
+    </member>
+  {% endfor %}
+  </TargetGroups>
+</DescribeDBProxyTargetGroupsResult>
+</DescribeDBProxyTargetGroupsResponse>"""
+
+
+MODIFY_DB_PROXY_TARGET_GROUP = """<ModifyDBProxyTargetGroupResponse>
+<ModifyDBProxyTargetGroupResult>
+  <DBProxyTargetGroup>
+    {{ group.to_xml() }}
+  </DBProxyTargetGroup>
+</ModifyDBProxyTargetGroupResult>
+</ModifyDBProxyTargetGroupResponse>"""
diff --git a/other_langs/terraform/rds/providers.tf b/other_langs/terraform/rds/providers.tf
index 2da69969b..228ab7576 100644
--- a/other_langs/terraform/rds/providers.tf
+++ b/other_langs/terraform/rds/providers.tf
@@ -6,7 +6,10 @@ provider "aws" {
   skip_requesting_account_id  = true
 
   endpoints {
-    rds = "http://localhost:5000"
+    ec2            = "http://localhost:5000"
+    iam            = "http://localhost:5000"
+    rds            = "http://localhost:5000"
+    secretsmanager = "http://localhost:5000"
   }
 
   access_key = "my-access-key"
diff --git a/other_langs/terraform/rds/proxy.tf b/other_langs/terraform/rds/proxy.tf
new file mode 100644
index 000000000..22912af70
--- /dev/null
+++ b/other_langs/terraform/rds/proxy.tf
@@ -0,0 +1,94 @@
+data "aws_vpc" "default" {
+  default = true
+}
+
+data "aws_subnets" "all" {
+  filter {
+    name   = "vpc-id"
+    values = [data.aws_vpc.default.id]
+  }
+}
+
+resource "random_string" "this" {
+  length  = 20
+  special = true
+  upper   = true
+}
+
+resource "aws_secretsmanager_secret" "test" {
+  name = "rds/test/test_admin"
+}
+
+resource "aws_secretsmanager_secret_version" "test" {
+  secret_id = aws_secretsmanager_secret.test.id
+  secret_string = jsonencode({
+    username = "test_admin"
+    password = "${random_string.this.result}"
+  })
+}
+
+data "aws_iam_policy_document" "rds_trust_policy" {
+  statement {
+    effect = "Allow"
+
+    principals {
+      type        = "Service"
+      identifiers = ["rds.amazonaws.com"]
+    }
+
+    actions = [
+      "sts:AssumeRole",
+    ]
+  }
+}
+
+resource "aws_iam_role" "proxy" {
+  assume_role_policy = data.aws_iam_policy_document.rds_trust_policy.json
+  name               = "rds-proxy-${aws_db_instance.test.db_name}"
+}
+
+resource "aws_db_instance" "test" {
+  allocated_storage   = 10
+  db_name             = "testdb"
+  engine              = "postgres"
+  engine_version      = "15"
+  instance_class      = "db.t3.micro"
+  username            = "test"
+  password            = random_string.this.result
+  skip_final_snapshot = true
+}
+
+resource "aws_db_proxy" "main" {
+  name                   = aws_db_instance.test.db_name
+  debug_logging          = false
+  engine_family          = "POSTGRESQL"
+  idle_client_timeout    = 1800
+  require_tls            = true
+  role_arn               = aws_iam_role.proxy.arn
+  vpc_security_group_ids = []
+  vpc_subnet_ids         = data.aws_subnets.all.ids
+
+  auth {
+    auth_scheme = "SECRETS"
+    description = "Secrets through IAM Authentication"
+    iam_auth    = "REQUIRED"
+    secret_arn  = aws_secretsmanager_secret.test.arn
+  }
+}
+
+resource "aws_db_proxy_default_target_group" "main" {
+  db_proxy_name = aws_db_proxy.main.name
+
+  connection_pool_config {
+    connection_borrow_timeout    = 120
+    max_connections_percent      = 100
+    max_idle_connections_percent = 50
+    session_pinning_filters      = ["EXCLUDE_VARIABLE_SETS"]
+  }
+}
+
+resource "aws_db_proxy_target" "main" {
+  db_instance_identifier = aws_db_instance.test.identifier
+  db_proxy_name          = aws_db_proxy.main.name
+  target_group_name      = aws_db_proxy_default_target_group.main.name
+}


Test patch:
diff --git a/.github/workflows/tests_terraform_examples.yml b/.github/workflows/tests_terraform_examples.yml
index d83ef22cc..26d59d99b 100644
--- a/.github/workflows/tests_terraform_examples.yml
+++ b/.github/workflows/tests_terraform_examples.yml
@@ -45,6 +45,7 @@ jobs:
         docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
         python scripts/ci_wait_for_server.py
     - name: Run tests
+      if: ${{ matrix.service != 'rds' }}
       run: |
         mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
         cd other_langs/terraform/${{ matrix.service }}
@@ -56,3 +57,11 @@ jobs:
         sleep 30
         terraform plan -detailed-exitcode
         terraform apply -destroy --auto-approve
+    - name: Run tests
+      if: ${{ matrix.service == 'rds' }}
+      run: |
+        mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
+        cd other_langs/terraform/${{ matrix.service }}
+        terraform init
+        terraform apply --auto-approve
+        terraform apply -destroy --auto-approve
diff --git a/tests/test_rds/test_rds_proxy.py b/tests/test_rds/test_rds_proxy.py
index 602289a5e..b2fe6aee5 100644
--- a/tests/test_rds/test_rds_proxy.py
+++ b/tests/test_rds/test_rds_proxy.py
@@ -39,14 +39,12 @@ def test_create_db_proxy():
     )
     db_proxy = resp["DBProxy"]
     assert db_proxy["DBProxyName"] == "testrdsproxy"
-    assert (
-        db_proxy["DBProxyArn"]
-        == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxy"
+    assert db_proxy["DBProxyArn"].startswith(
+        f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:"
     )
     assert db_proxy["Status"] == "available"
     assert db_proxy["EngineFamily"] == "MYSQL"
     assert db_proxy["VpcId"] == vpc_id
-    assert db_proxy["VpcSecurityGroupIds"] == []
     assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2]
     assert db_proxy["Auth"] == [
         {
@@ -99,14 +97,12 @@ def test_describe_db_proxies():
     response = rds_client.describe_db_proxies(DBProxyName="testrdsproxydescribe")
     db_proxy = response["DBProxies"][0]
     assert db_proxy["DBProxyName"] == "testrdsproxydescribe"
-    assert (
-        db_proxy["DBProxyArn"]
-        == f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:testrdsproxydescribe"
+    assert db_proxy["DBProxyArn"].startswith(
+        f"arn:aws:rds:us-west-2:{ACCOUNT_ID}:db-proxy:prx-"
     )
     assert db_proxy["Status"] == "available"
     assert db_proxy["EngineFamily"] == "MYSQL"
     assert db_proxy["VpcId"] == vpc_id
-    assert db_proxy["VpcSecurityGroupIds"] == []
     assert db_proxy["VpcSubnetIds"] == [subnet_id, subnet_id_2]
     assert db_proxy["Auth"] == [
         {
diff --git a/tests/test_rds/test_rds_proxy_target_groups.py b/tests/test_rds/test_rds_proxy_target_groups.py
new file mode 100644
index 000000000..a38b70617
--- /dev/null
+++ b/tests/test_rds/test_rds_proxy_target_groups.py
@@ -0,0 +1,245 @@
+import json
+from time import sleep
+from uuid import uuid4
+
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+
+from moto import mock_aws
+from tests import allow_aws_request
+
+DEFAULT_REGION = "us-east-1"
+
+ASSUME_ROLE_POLICY = {
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Principal": {"Service": "rds.amazonaws.com"},
+            "Action": "sts:AssumeRole",
+        }
+    ],
+}
+
+
+@pytest.fixture(scope="module")
+def mockaws():
+    if allow_aws_request():
+        yield
+    else:
+        with mock_aws():
+            yield
+
+
+@pytest.fixture(scope="module")
+def secrets_arn():
+    client = boto3.client("secretsmanager", DEFAULT_REGION)
+    secret_name = f"moto-test-{str(uuid4())[0:6]}"
+
+    try:
+        secret = client.create_secret(Name=secret_name, SecretString="ss")
+        yield secret["ARN"]
+    finally:
+        client.delete_secret(SecretId=secret_name)
+
+
+@pytest.fixture(scope="module")
+def vpc_id(mockaws):  # pylint: disable=redefined-outer-name
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    _vpc_id = None
+    try:
+        _vpc_id = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]["VpcId"]
+        yield _vpc_id
+    finally:
+        ec2_client.delete_vpc(VpcId=_vpc_id)
+
+
+@pytest.fixture(scope="module")
+def subnet_id1(vpc_id):  # pylint: disable=redefined-outer-name
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    try:
+        subnet = ec2_client.create_subnet(
+            CidrBlock="10.0.1.0/24", VpcId=vpc_id, AvailabilityZone="us-east-1a"
+        )
+        subnet_id = subnet["Subnet"]["SubnetId"]
+        yield subnet_id
+    finally:
+        ec2_client.delete_subnet(SubnetId=subnet_id)
+
+
+@pytest.fixture(scope="module")
+def subnet_id2(vpc_id):  # pylint: disable=redefined-outer-name
+    ec2_client = boto3.client("ec2", region_name=DEFAULT_REGION)
+    try:
+        subnet = ec2_client.create_subnet(
+            CidrBlock="10.0.2.0/24", VpcId=vpc_id, AvailabilityZone="us-east-1b"
+        )
+        subnet_id = subnet["Subnet"]["SubnetId"]
+        yield subnet_id
+    finally:
+        ec2_client.delete_subnet(SubnetId=subnet_id)
+
+
+@pytest.fixture(scope="module")
+def role_arn(mockaws):  # pylint: disable=redefined-outer-name
+    role_name = f"moto-test-{str(uuid4())[0:6]}"
+    iam = boto3.client("iam", region_name=DEFAULT_REGION)
+    try:
+        role = iam.create_role(
+            RoleName=role_name,
+            AssumeRolePolicyDocument=json.dumps(ASSUME_ROLE_POLICY),
+        )["Role"]
+        yield role["Arn"]
+    finally:
+        iam.delete_role(RoleName=role_name)
+
+
+@pytest.fixture(scope="module")
+def proxy_name(subnet_id1, subnet_id2, role_arn, secrets_arn):  # pylint: disable=redefined-outer-name
+    name = f"moto-test-{str(uuid4())[0:6]}"
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    proxy_creation_succeeded = False
+    try:
+        resp = rds_client.create_db_proxy(
+            DBProxyName=name,
+            EngineFamily="POSTGRESQL",
+            Auth=[
+                {
+                    # "UserName": "user1",
+                    # "AuthScheme": "SECRETS",
+                    "IAMAuth": "DISABLED",
+                    "SecretArn": secrets_arn,
+                }
+            ],
+            RoleArn=role_arn,
+            VpcSubnetIds=[subnet_id1, subnet_id2],
+        )
+        status = resp["DBProxy"]["Status"]
+        while status.lower() == "creating":
+            sleep(5)
+            status = rds_client.describe_db_proxies(DBProxyName=name)["DBProxies"][0][
+                "Status"
+            ]
+        proxy_creation_succeeded = True
+
+        yield name
+    finally:
+        if proxy_creation_succeeded:
+            rds_client.delete_db_proxy(DBProxyName=name)
+            deleted = False
+            while not deleted:
+                try:
+                    rds_client.describe_db_proxies(DBProxyName=name)
+                    sleep(10)
+                except (ClientError, IndexError):
+                    deleted = True
+
+
+@pytest.fixture(scope="module")
+def db_cluster_id(mockaws):  # pylint: disable=redefined-outer-name
+    cluster_name = f"moto-test-{str(uuid4())[0:6]}"
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    cluster_creation_succeeded = False
+    try:
+        cluster_id = rds_client.create_db_cluster(
+            DBClusterIdentifier=cluster_name,
+            Engine="aurora-postgresql",
+            MasterUsername="root",
+            MasterUserPassword="hunter21",
+        )["DBCluster"]["DBClusterIdentifier"]
+        rds_client.get_waiter("db_cluster_available").wait(
+            DBClusterIdentifier=cluster_id
+        )
+        cluster_creation_succeeded = True
+        yield cluster_id
+    finally:
+        if cluster_creation_succeeded:
+            rds_client.delete_db_cluster(
+                DBClusterIdentifier=cluster_id, SkipFinalSnapshot=True
+            )
+
+
+def test_default_proxy_targets(account_id, proxy_name):  # pylint: disable=redefined-outer-name
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+    resp = rds_client.describe_db_proxy_targets(DBProxyName=proxy_name)
+
+    assert resp["Targets"] == []
+
+    groups = rds_client.describe_db_proxy_target_groups(
+        DBProxyName=proxy_name,
+        TargetGroupName="default",
+    )["TargetGroups"]
+    assert len(groups) == 1
+    groups[0].pop("CreatedDate")
+    groups[0].pop("UpdatedDate")
+    target_group_arn = groups[0].pop("TargetGroupArn")
+    assert target_group_arn.startswith(
+        f"arn:aws:rds:us-east-1:{account_id}:target-group:prx-tg-"
+    )  # 17 more chars (lowercase + digits)
+    assert groups[0] == {
+        "DBProxyName": proxy_name,
+        "TargetGroupName": "default",
+        "IsDefault": True,
+        "Status": "available",
+        "ConnectionPoolConfig": {
+            "MaxConnectionsPercent": 100,
+            "MaxIdleConnectionsPercent": 50,
+            "ConnectionBorrowTimeout": 120,
+            "SessionPinningFilters": [],
+        },
+    }
+
+
+def test_register_db_proxy(account_id, proxy_name, db_cluster_id):  # pylint: disable=redefined-outer-name
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+
+    resp = rds_client.register_db_proxy_targets(
+        DBProxyName=proxy_name,
+        DBClusterIdentifiers=[db_cluster_id],
+    )["DBProxyTargets"]
+    resp[0].pop("Endpoint", None)
+    assert resp == [
+        {
+            "RdsResourceId": db_cluster_id,
+            "Port": 5432,
+            "Type": "TRACKED_CLUSTER",
+            "TargetHealth": {"State": "REGISTERING"},
+        }
+    ]
+
+    resp = rds_client.describe_db_proxy_targets(DBProxyName=proxy_name)["Targets"]
+    resp[0].pop("Endpoint", None)
+    resp[0].pop("TargetHealth", None)
+    assert resp == [
+        {"RdsResourceId": db_cluster_id, "Port": 5432, "Type": "TRACKED_CLUSTER"}
+    ]
+
+    rds_client.deregister_db_proxy_targets(
+        DBProxyName=proxy_name,
+        DBClusterIdentifiers=[db_cluster_id],
+    )
+
+    resp = rds_client.describe_db_proxy_targets(DBProxyName=proxy_name)
+    assert resp["Targets"] == []
+
+
+def test_modify_group(proxy_name):  # pylint: disable=redefined-outer-name
+    rds_client = boto3.client("rds", region_name=DEFAULT_REGION)
+
+    rds_client.modify_db_proxy_target_group(
+        TargetGroupName="default",
+        DBProxyName=proxy_name,
+        ConnectionPoolConfig={
+            "MaxConnectionsPercent": 27,
+            "SessionPinningFilters": ["filter1"],
+        },
+    )
+
+    group = rds_client.describe_db_proxy_target_groups(
+        DBProxyName=proxy_name,
+        TargetGroupName="default",
+    )["TargetGroups"][0]
+    assert group["ConnectionPoolConfig"]["MaxConnectionsPercent"] == 27
+    assert group["ConnectionPoolConfig"]["MaxIdleConnectionsPercent"] == 13
+    assert group["ConnectionPoolConfig"]["SessionPinningFilters"] == ["filter1"]