Repository: getmoto/moto
Pull request #6808 (2023-09-13 08:46:59)

ELBv2: added validation for target group
# Motivation
- This PR aims to make target group in parity with AWS.

# Added
- Parameter validation for `create_target_group`
- Default values for `create_target_group`
- Adjusted test cases accordingly
- Fixed `create_target_group` response

# Notes
- Related PR for AWS Validated Test Case: https://github.com/localstack/localstack-ext/pull/2064

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
> Merging [#6808](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) (aef2430) into [master](https://app.codecov.io/gh/getmoto/moto/commit/be2f45ed8b553072d0a41aa97cacf8e292075202?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) (be2f45e) will **decrease** coverage by `0.01%`.
> The diff coverage is `83.92%`.

```diff
@@            Coverage Diff             @@
##           master    #6808      +/-   ##
==========================================
- Coverage   96.20%   96.19%   -0.01%     
==========================================
  Files         816      816              
  Lines       79951    79990      +39     
==========================================
+ Hits        76914    76945      +31     
- Misses       3037     3045       +8     
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/6808/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/6808/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `37.01% <3.57%> (-0.01%)` | :arrow_down: |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/6808/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `96.13% <83.92%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [moto/elbv2/exceptions.py](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9lbGJ2Mi9leGNlcHRpb25zLnB5) | `88.88% <66.66%> (-0.77%)` | :arrow_down: |
| [moto/elbv2/models.py](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9lbGJ2Mi9tb2RlbHMucHk=) | `92.20% <84.61%> (-0.50%)` | :arrow_down: |
| [moto/elbv2/responses.py](https://app.codecov.io/gh/getmoto/moto/pull/6808?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#diff-bW90by9lbGJ2Mi9yZXNwb25zZXMucHk=) | `99.44% <100.00%> (+<0.01%)` | :arrow_up: |



Patch:
diff --git a/moto/core/responses.py b/moto/core/responses.py
index 67c2cb6cd..42b852035 100644
--- a/moto/core/responses.py
+++ b/moto/core/responses.py
@@ -157,10 +157,10 @@ class ActionAuthenticatorMixin(object):
         else:
             ActionAuthenticatorMixin.request_count += 1
 
-    def _authenticate_and_authorize_normal_action(self) -> None:
+    def _authenticate_and_authorize_normal_action(self, resource: str = "*") -> None:
         from moto.iam.access_control import IAMRequest
 
-        self._authenticate_and_authorize_action(IAMRequest)
+        self._authenticate_and_authorize_action(IAMRequest, resource)
 
     def _authenticate_and_authorize_s3_action(
         self, bucket_name: Optional[str] = None, key_name: Optional[str] = None
@@ -491,9 +491,13 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
 
     def call_action(self) -> TYPE_RESPONSE:
         headers = self.response_headers
+        if hasattr(self, "_determine_resource"):
+            resource = self._determine_resource()
+        else:
+            resource = "*"
 
         try:
-            self._authenticate_and_authorize_normal_action()
+            self._authenticate_and_authorize_normal_action(resource)
         except HTTPException as http_error:
             response = http_error.description, dict(status=http_error.code)
             return self._send_response(headers, response)
diff --git a/moto/elbv2/exceptions.py b/moto/elbv2/exceptions.py
index 22ae9fe8c..9550fcb9e 100644
--- a/moto/elbv2/exceptions.py
+++ b/moto/elbv2/exceptions.py
@@ -184,3 +184,8 @@ class InvalidStatusCodeActionTypeError(ELBClientError):
 class InvalidLoadBalancerActionException(ELBClientError):
     def __init__(self, msg: str):
         super().__init__("InvalidLoadBalancerAction", msg)
+
+
+class ValidationError(ELBClientError):
+    def __init__(self, msg: str):
+        super().__init__("ValidationError", msg)
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
index a54f7726f..0a45bd9e9 100644
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -35,6 +35,7 @@ from .exceptions import (
     InvalidModifyRuleArgumentsError,
     InvalidStatusCodeActionTypeError,
     InvalidLoadBalancerActionException,
+    ValidationError,
 )
 
 ALLOWED_ACTIONS = [
@@ -79,42 +80,49 @@ class FakeTargetGroup(CloudFormationModel):
         healthcheck_port: Optional[str] = None,
         healthcheck_path: Optional[str] = None,
         healthcheck_interval_seconds: Optional[str] = None,
-        healthcheck_timeout_seconds: Optional[int] = None,
+        healthcheck_timeout_seconds: Optional[str] = None,
         healthcheck_enabled: Optional[str] = None,
         healthy_threshold_count: Optional[str] = None,
         unhealthy_threshold_count: Optional[str] = None,
         matcher: Optional[Dict[str, Any]] = None,
         target_type: Optional[str] = None,
+        ip_address_type: Optional[str] = None,
     ):
         # TODO: default values differs when you add Network Load balancer
         self.name = name
         self.arn = arn
         self.vpc_id = vpc_id
-        self.protocol = protocol
-        self.protocol_version = protocol_version or "HTTP1"
+        if target_type == "lambda":
+            self.protocol = None
+            self.protocol_version = None
+        elif target_type == "alb":
+            self.protocol = "TCP"
+            self.protocol_version = None
+        else:
+            self.protocol = protocol
+            self.protocol_version = protocol_version
         self.port = port
         self.healthcheck_protocol = healthcheck_protocol or self.protocol
-        self.healthcheck_port = healthcheck_port
+        self.healthcheck_port = healthcheck_port or "traffic-port"
         self.healthcheck_path = healthcheck_path
-        self.healthcheck_interval_seconds = healthcheck_interval_seconds or 30
-        self.healthcheck_timeout_seconds = healthcheck_timeout_seconds
-        if not healthcheck_timeout_seconds:
-            # Default depends on protocol
-            if protocol in ["TCP", "TLS"]:
-                self.healthcheck_timeout_seconds = 6
-            elif protocol in ["HTTP", "HTTPS", "GENEVE"]:
-                self.healthcheck_timeout_seconds = 5
-            else:
-                self.healthcheck_timeout_seconds = 30
-        self.healthcheck_enabled = healthcheck_enabled
-        self.healthy_threshold_count = healthy_threshold_count or 5
-        self.unhealthy_threshold_count = unhealthy_threshold_count or 2
+        self.healthcheck_interval_seconds = healthcheck_interval_seconds or "30"
+        self.healthcheck_timeout_seconds = healthcheck_timeout_seconds or "10"
+        self.ip_address_type = (
+            ip_address_type or "ipv4" if self.protocol != "GENEVE" else None
+        )
+        self.healthcheck_enabled = (
+            healthcheck_enabled.lower() == "true"
+            if healthcheck_enabled in ["true", "false"]
+            else True
+        )
+        self.healthy_threshold_count = healthy_threshold_count or "5"
+        self.unhealthy_threshold_count = unhealthy_threshold_count or "2"
         self.load_balancer_arns: List[str] = []
         if self.healthcheck_protocol != "TCP":
             self.matcher: Dict[str, Any] = matcher or {"HttpCode": "200"}
-            self.healthcheck_path = self.healthcheck_path or "/"
+            self.healthcheck_path = self.healthcheck_path
             self.healthcheck_port = self.healthcheck_port or str(self.port)
-        self.target_type = target_type
+        self.target_type = target_type or "instance"
 
         self.attributes = {
             "deregistration_delay.timeout_seconds": 300,
@@ -1030,6 +1038,9 @@ Member must satisfy regular expression pattern: {expression}"
             )
 
     def create_target_group(self, name: str, **kwargs: Any) -> FakeTargetGroup:
+        protocol = kwargs.get("protocol")
+        target_type = kwargs.get("target_type")
+
         if len(name) > 32:
             raise InvalidTargetGroupNameError(
                 f"Target group name '{name}' cannot be longer than '32' characters"
@@ -1080,6 +1091,124 @@ Member must satisfy regular expression pattern: {expression}"
                 "HttpCode must be like 200 | 200-399 | 200,201 ...",
             )
 
+        if target_type in ("instance", "ip", "alb"):
+            for param in ("protocol", "port", "vpc_id"):
+                if not kwargs.get(param):
+                    param = "VPC ID" if param == "vpc_id" else param.lower()
+                    raise ValidationError(f"A {param} must be specified")
+
+        if kwargs.get("vpc_id"):
+            from moto.ec2.exceptions import InvalidVPCIdError
+
+            try:
+                self.ec2_backend.get_vpc(kwargs.get("vpc_id"))
+            except InvalidVPCIdError:
+                raise ValidationError(
+                    f"The VPC ID '{kwargs.get('vpc_id')}' is not found"
+                )
+
+        kwargs_patch = {}
+
+        conditions: Dict[str, Any] = {
+            "target_lambda": {
+                "healthcheck_interval_seconds": 35,
+                "healthcheck_timeout_seconds": 30,
+                "unhealthy_threshold_count": 2,
+                "healthcheck_enabled": "false",
+                "healthcheck_path": "/",
+            },
+            "target_alb": {
+                "healthcheck_protocol": "HTTP",
+                "healthcheck_path": "/",
+                "healthcheck_timeout_seconds": 6,
+                "matcher": {"HttpCode": "200-399"},
+            },
+            "protocol_GENEVE": {
+                "healthcheck_interval_seconds": 10,
+                "healthcheck_port": 80,
+                "healthcheck_timeout_seconds": 5,
+                "healthcheck_protocol": "TCP",
+                "unhealthy_threshold_count": 2,
+            },
+            "protocol_HTTP_HTTPS": {
+                "healthcheck_timeout_seconds": 5,
+                "protocol_version": "HTTP1",
+                "healthcheck_path": "/",
+                "unhealthy_threshold_count": 2,
+                "healthcheck_interval_seconds": 30,
+            },
+            "protocol_TCP": {
+                "healthcheck_timeout_seconds": 10,
+            },
+            "protocol_TCP_TCP_UDP_UDP_TLS": {
+                "healthcheck_protocol": "TCP",
+                "unhealthy_threshold_count": 2,
+                "healthcheck_interval_seconds": 30,
+            },
+        }
+
+        if target_type == "lambda":
+            kwargs_patch.update(
+                {k: kwargs.get(k) or v for k, v in conditions["target_lambda"].items()}
+            )
+
+        if protocol == "GENEVE":
+            kwargs_patch.update(
+                {
+                    k: kwargs.get(k) or v
+                    for k, v in conditions["protocol_GENEVE"].items()
+                }
+            )
+
+        if protocol in ("HTTP", "HTTPS"):
+            kwargs_patch.update(
+                {
+                    k: kwargs.get(k) or v
+                    for k, v in conditions["protocol_HTTP_HTTPS"].items()
+                }
+            )
+
+        if protocol == "TCP":
+            kwargs_patch.update(
+                {k: kwargs.get(k) or v for k, v in conditions["protocol_TCP"].items()}
+            )
+
+        if protocol in ("TCP", "TCP_UDP", "UDP", "TLS"):
+            kwargs_patch.update(
+                {
+                    k: kwargs.get(k) or v
+                    for k, v in conditions["protocol_TCP_TCP_UDP_UDP_TLS"].items()
+                }
+            )
+
+        if target_type == "alb":
+            kwargs_patch.update(
+                {k: kwargs.get(k) or v for k, v in conditions["target_alb"].items()}
+            )
+
+        kwargs.update(kwargs_patch)
+
+        healthcheck_timeout_seconds = int(
+            str(kwargs.get("healthcheck_timeout_seconds") or "10")
+        )
+        healthcheck_interval_seconds = int(
+            str(kwargs.get("healthcheck_interval_seconds") or "30")
+        )
+
+        if (
+            healthcheck_timeout_seconds is not None
+            and healthcheck_interval_seconds is not None
+        ):
+
+            if healthcheck_interval_seconds < healthcheck_timeout_seconds:
+                raise ValidationError(
+                    "Health check interval must be greater than the timeout."
+                )
+            if healthcheck_interval_seconds == healthcheck_timeout_seconds:
+                raise ValidationError(
+                    f"Health check timeout '{healthcheck_timeout_seconds}' must be smaller than the interval '{healthcheck_interval_seconds}'"
+                )
+
         arn = make_arn_for_target_group(
             account_id=self.account_id, name=name, region_name=self.region_name
         )
@@ -1524,11 +1653,11 @@ Member must satisfy regular expression pattern: {expression}"
         health_check_port: Optional[str] = None,
         health_check_path: Optional[str] = None,
         health_check_interval: Optional[str] = None,
-        health_check_timeout: Optional[int] = None,
+        health_check_timeout: Optional[str] = None,
         healthy_threshold_count: Optional[str] = None,
         unhealthy_threshold_count: Optional[str] = None,
         http_codes: Optional[str] = None,
-        health_check_enabled: Optional[str] = None,
+        health_check_enabled: Optional[bool] = None,
     ) -> FakeTargetGroup:
         target_group = self.target_groups.get(arn)
         if target_group is None:
diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py
index f4ebba781..652d5b9b9 100644
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -185,7 +185,7 @@ class ELBV2Response(BaseResponse):
         name = params.get("Name")
         vpc_id = params.get("VpcId")
         protocol = params.get("Protocol")
-        protocol_version = params.get("ProtocolVersion", "HTTP1")
+        protocol_version = params.get("ProtocolVersion")
         port = params.get("Port")
         healthcheck_protocol = self._get_param("HealthCheckProtocol")
         healthcheck_port = self._get_param("HealthCheckPort")
@@ -196,7 +196,8 @@ class ELBV2Response(BaseResponse):
         healthy_threshold_count = self._get_param("HealthyThresholdCount")
         unhealthy_threshold_count = self._get_param("UnhealthyThresholdCount")
         matcher = params.get("Matcher")
-        target_type = params.get("TargetType")
+        target_type = params.get("TargetType", "instance")
+        ip_address_type = params.get("IpAddressType")
         tags = params.get("Tags")
 
         target_group = self.elbv2_backend.create_target_group(
@@ -214,6 +215,7 @@ class ELBV2Response(BaseResponse):
             healthy_threshold_count=healthy_threshold_count,
             unhealthy_threshold_count=unhealthy_threshold_count,
             matcher=matcher,
+            ip_address_type=ip_address_type,
             target_type=target_type,
             tags=tags,
         )
@@ -797,6 +799,9 @@ CREATE_TARGET_GROUP_TEMPLATE = """<CreateTargetGroupResponse xmlns="http://elast
         <TargetGroupName>{{ target_group.name }}</TargetGroupName>
         {% if target_group.protocol %}
         <Protocol>{{ target_group.protocol }}</Protocol>
+        {% if target_group.protocol_version %}
+        <ProtocolVersion>{{ target_group.protocol_version }}</ProtocolVersion>
+        {% endif %}
         {% endif %}
         {% if target_group.port %}
         <Port>{{ target_group.port }}</Port>
@@ -804,14 +809,22 @@ CREATE_TARGET_GROUP_TEMPLATE = """<CreateTargetGroupResponse xmlns="http://elast
         {% if target_group.vpc_id %}
         <VpcId>{{ target_group.vpc_id }}</VpcId>
         {% endif %}
-        <HealthCheckProtocol>{{ target_group.healthcheck_protocol }}</HealthCheckProtocol>
-        {% if target_group.healthcheck_port %}<HealthCheckPort>{{ target_group.healthcheck_port }}</HealthCheckPort>{% endif %}
+        {% if target_group.healthcheck_enabled %}
+        {% if target_group.healthcheck_port %}
+        <HealthCheckPort>{{ target_group.healthcheck_port }}</HealthCheckPort>
+        {% endif %}
+        {% if target_group.healthcheck_protocol %}
+        <HealthCheckProtocol>{{ target_group.healthcheck_protocol or "None" }}</HealthCheckProtocol>
+        {% endif %}
+        {% endif %}
+        {% if target_group.healthcheck_path %}
         <HealthCheckPath>{{ target_group.healthcheck_path or '' }}</HealthCheckPath>
+        {% endif %}
         <HealthCheckIntervalSeconds>{{ target_group.healthcheck_interval_seconds }}</HealthCheckIntervalSeconds>
         <HealthCheckTimeoutSeconds>{{ target_group.healthcheck_timeout_seconds }}</HealthCheckTimeoutSeconds>
-        <HealthCheckEnabled>{{ target_group.healthcheck_enabled and 'true' or 'false' }}</HealthCheckEnabled>
         <HealthyThresholdCount>{{ target_group.healthy_threshold_count }}</HealthyThresholdCount>
         <UnhealthyThresholdCount>{{ target_group.unhealthy_threshold_count }}</UnhealthyThresholdCount>
+        <HealthCheckEnabled>{{ target_group.healthcheck_enabled and 'true' or 'false' }}</HealthCheckEnabled>
         {% if target_group.matcher %}
         <Matcher>
           <HttpCode>{{ target_group.matcher['HttpCode'] }}</HttpCode>
@@ -820,6 +833,9 @@ CREATE_TARGET_GROUP_TEMPLATE = """<CreateTargetGroupResponse xmlns="http://elast
         {% if target_group.target_type %}
         <TargetType>{{ target_group.target_type }}</TargetType>
         {% endif %}
+        {% if target_group.ip_address_type %}
+        <IpAddressType>{{ target_group.ip_address_type }}</IpAddressType>
+        {% endif %}
       </member>
     </TargetGroups>
   </CreateTargetGroupResult>
diff --git a/moto/iam/access_control.py b/moto/iam/access_control.py
index 70b506260..2627a4b7a 100644
--- a/moto/iam/access_control.py
+++ b/moto/iam/access_control.py
@@ -380,7 +380,7 @@ class IAMPolicy:
                     permitted = True
         else:  # dict
             iam_policy_statement = IAMPolicyStatement(self._policy_json["Statement"])
-            return iam_policy_statement.is_action_permitted(action)
+            return iam_policy_statement.is_action_permitted(action, resource)
 
         if permitted:
             return PermissionResult.PERMITTED
@@ -408,6 +408,8 @@ class IAMPolicyStatement:
             if self.is_unknown_principal(self._statement.get("Principal")):
                 return PermissionResult.NEUTRAL
             same_resource = self._check_element_matches("Resource", resource)
+            if not same_resource:
+                return PermissionResult.NEUTRAL
             if self._statement["Effect"] == "Allow" and same_resource:
                 return PermissionResult.PERMITTED
             else:  # Deny
diff --git a/moto/iam/models.py b/moto/iam/models.py
index 5346d652a..023bcb493 100644
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -84,9 +84,17 @@ def mark_account_as_visited(
 ) -> None:
     account = iam_backends[account_id]
     if access_key in account["global"].access_keys:
-        account["global"].access_keys[access_key].last_used = AccessKeyLastUsed(
+        key = account["global"].access_keys[access_key]
+        key.last_used = AccessKeyLastUsed(
             timestamp=utcnow(), service=service, region=region
         )
+        if key.role_arn:
+            try:
+                role = account["global"].get_role_by_arn(key.role_arn)
+                role.last_used = utcnow()
+            except IAMNotFoundException:
+                # User assumes a non-existing role
+                pass
     else:
         # User provided access credentials unknown to us
         pass
@@ -1082,6 +1090,7 @@ class AccessKey(CloudFormationModel):
         self.status = status
         self.create_date = utcnow()
         self.last_used: Optional[datetime] = None
+        self.role_arn: Optional[str] = None
 
     @property
     def created_iso_8601(self) -> str:
diff --git a/moto/sts/models.py b/moto/sts/models.py
index 5423761c5..b465b0039 100644
--- a/moto/sts/models.py
+++ b/moto/sts/models.py
@@ -108,6 +108,7 @@ class STSBackend(BaseBackend):
             duration,
             external_id,
         )
+        access_key.role_arn = role_arn
         account_backend = sts_backends[account_id]["global"]
         account_backend.assumed_roles.append(role)
         return role
diff --git a/moto/sts/responses.py b/moto/sts/responses.py
index c54481652..7e95e6535 100644
--- a/moto/sts/responses.py
+++ b/moto/sts/responses.py
@@ -13,6 +13,11 @@ class TokenResponse(BaseResponse):
     def backend(self) -> STSBackend:
         return sts_backends[self.current_account]["global"]
 
+    def _determine_resource(self) -> str:
+        if "AssumeRole" in self.querystring.get("Action", []):
+            return self.querystring.get("RoleArn")[0]  # type: ignore[index]
+        return "*"
+
     def get_session_token(self) -> str:
         duration = int(self.querystring.get("DurationSeconds", [43200])[0])
         token = self.backend.get_session_token(duration=duration)


Test patch:
diff --git a/tests/test_autoscaling/test_elbv2.py b/tests/test_autoscaling/test_elbv2.py
index 0ab897c2c..074a0bd7f 100644
--- a/tests/test_autoscaling/test_elbv2.py
+++ b/tests/test_autoscaling/test_elbv2.py
@@ -27,7 +27,7 @@ class TestAutoscalignELBv2(unittest.TestCase):
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
diff --git a/tests/test_core/test_auth.py b/tests/test_core/test_auth.py
index b2a051112..8aceb2cf5 100644
--- a/tests/test_core/test_auth.py
+++ b/tests/test_core/test_auth.py
@@ -291,6 +291,47 @@ def test_access_denied_with_not_allowing_policy():
     )
 
 
+@set_initial_no_auth_action_count(3)
+@mock_sts
+def test_access_denied_explicitly_on_specific_resource():
+    user_name = "test-user"
+    forbidden_role_arn = f"arn:aws:iam::{ACCOUNT_ID}:role/forbidden_explicitly"
+    allowed_role_arn = f"arn:aws:iam::{ACCOUNT_ID}:role/allowed_implictly"
+    role_session_name = "dummy"
+    inline_policy_document = {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Deny",
+                "Action": ["sts:AssumeRole"],
+                "Resource": forbidden_role_arn,
+            },
+            {"Effect": "Allow", "Action": ["sts:AssumeRole"], "Resource": "*"},
+        ],
+    }
+    access_key = create_user_with_access_key_and_inline_policy(
+        user_name, inline_policy_document
+    )
+    client = boto3.client(
+        "sts",
+        region_name="us-east-1",
+        aws_access_key_id=access_key["AccessKeyId"],
+        aws_secret_access_key=access_key["SecretAccessKey"],
+    )
+    with pytest.raises(ClientError) as ex:
+        client.assume_role(
+            RoleArn=forbidden_role_arn, RoleSessionName=role_session_name
+        )
+    assert ex.value.response["Error"]["Code"] == "AccessDenied"
+    assert ex.value.response["ResponseMetadata"]["HTTPStatusCode"] == 403
+    assert (
+        ex.value.response["Error"]["Message"]
+        == f"User: arn:aws:iam::{ACCOUNT_ID}:user/{user_name} is not authorized to perform: sts:AssumeRole"
+    )
+    # Not raising means success
+    client.assume_role(RoleArn=allowed_role_arn, RoleSessionName=role_session_name)
+
+
 @set_initial_no_auth_action_count(3)
 @mock_ec2
 def test_access_denied_for_run_instances():
diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py
index 978476249..19db045e6 100644
--- a/tests/test_elbv2/test_elbv2.py
+++ b/tests/test_elbv2/test_elbv2.py
@@ -266,7 +266,7 @@ def test_create_listeners_without_port():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -445,7 +445,7 @@ def test_register_targets():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -551,7 +551,7 @@ def test_stopped_instance_target():
         HealthCheckProtocol="HTTP",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -580,7 +580,7 @@ def test_stopped_instance_target():
     target_health_description = response["TargetHealthDescriptions"][0]
 
     assert target_health_description["Target"] == target_dict
-    assert target_health_description["HealthCheckPort"] == str(target_group_port)
+    assert target_health_description["HealthCheckPort"] == "traffic-port"
     assert target_health_description["TargetHealth"] == {"State": "healthy"}
 
     instance.stop()
@@ -591,7 +591,7 @@ def test_stopped_instance_target():
     assert len(response["TargetHealthDescriptions"]) == 1
     target_health_description = response["TargetHealthDescriptions"][0]
     assert target_health_description["Target"] == target_dict
-    assert target_health_description["HealthCheckPort"] == str(target_group_port)
+    assert target_health_description["HealthCheckPort"] == "traffic-port"
     assert target_health_description["TargetHealth"] == {
         "State": "unused",
         "Reason": "Target.InvalidState",
@@ -634,7 +634,7 @@ def test_terminated_instance_target():
         HealthCheckProtocol="HTTP",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -663,7 +663,7 @@ def test_terminated_instance_target():
     target_health_description = response["TargetHealthDescriptions"][0]
 
     assert target_health_description["Target"] == target_dict
-    assert target_health_description["HealthCheckPort"] == str(target_group_port)
+    assert target_health_description["HealthCheckPort"] == "traffic-port"
     assert target_health_description["TargetHealth"] == {"State": "healthy"}
 
     instance.terminate()
@@ -772,7 +772,7 @@ def test_handle_listener_rules():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -1323,7 +1323,7 @@ def test_modify_listener_http_to_https():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -1523,7 +1523,9 @@ def test_add_listener_certificate():
 
     load_balancer_arn = response["LoadBalancers"][0]["LoadBalancerArn"]
 
-    response = client.create_target_group(Name="a-target", Protocol="HTTPS", Port=8443)
+    response = client.create_target_group(
+        Name="a-target", Protocol="HTTPS", Port=8443, VpcId=vpc.id
+    )
     target_group_arn = response["TargetGroups"][0]["TargetGroupArn"]
 
     # HTTPS listener
@@ -1564,10 +1566,12 @@ def test_add_listener_certificate():
 @mock_elbv2
 @mock_ec2
 def test_forward_config_action():
-    response, _, _, _, _, conn = create_load_balancer()
+    response, vpc, _, _, _, conn = create_load_balancer()
     load_balancer_arn = response["LoadBalancers"][0]["LoadBalancerArn"]
 
-    response = conn.create_target_group(Name="a-target", Protocol="HTTPS", Port=8443)
+    response = conn.create_target_group(
+        Name="a-target", Protocol="HTTPS", Port=8443, VpcId=vpc.id
+    )
     target_group_arn = response["TargetGroups"][0]["TargetGroupArn"]
 
     action = {
@@ -1600,10 +1604,12 @@ def test_forward_config_action():
 @mock_elbv2
 @mock_ec2
 def test_forward_config_action__with_stickiness():
-    response, _, _, _, _, conn = create_load_balancer()
+    response, vpc, _, _, _, conn = create_load_balancer()
     load_balancer_arn = response["LoadBalancers"][0]["LoadBalancerArn"]
 
-    response = conn.create_target_group(Name="a-target", Protocol="HTTPS", Port=8443)
+    response = conn.create_target_group(
+        Name="a-target", Protocol="HTTPS", Port=8443, VpcId=vpc.id
+    )
     target_group_arn = response["TargetGroups"][0]["TargetGroupArn"]
 
     action = {
diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py
index 4ad17c683..53e311e11 100644
--- a/tests/test_elbv2/test_elbv2_target_groups.py
+++ b/tests/test_elbv2/test_elbv2_target_groups.py
@@ -23,7 +23,7 @@ def test_create_target_group_with_invalid_healthcheck_protocol():
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
@@ -50,7 +50,7 @@ def test_create_target_group_with_tags():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -93,7 +93,7 @@ def test_create_target_group_and_listeners():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -245,7 +245,7 @@ def test_create_invalid_target_group_long_name():
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
@@ -277,7 +277,7 @@ def test_create_invalid_target_group_invalid_characters(name):
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
@@ -309,7 +309,7 @@ def test_create_invalid_target_group_alphanumeric_characters(name):
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
@@ -340,7 +340,7 @@ def test_create_valid_target_group_valid_names(name):
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -361,7 +361,7 @@ def test_target_group_attributes():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -433,7 +433,7 @@ def test_create_target_group_invalid_protocol():
             HealthCheckPort="8080",
             HealthCheckPath="/",
             HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
+            HealthCheckTimeoutSeconds=3,
             HealthyThresholdCount=5,
             UnhealthyThresholdCount=2,
             Matcher={"HttpCode": "200"},
@@ -474,7 +474,7 @@ def test_describe_target_groups_no_arguments():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "201"},
@@ -502,7 +502,7 @@ def test_modify_target_group():
         HealthCheckPort="8080",
         HealthCheckPath="/",
         HealthCheckIntervalSeconds=5,
-        HealthCheckTimeoutSeconds=5,
+        HealthCheckTimeoutSeconds=3,
         HealthyThresholdCount=5,
         UnhealthyThresholdCount=2,
         Matcher={"HttpCode": "200"},
@@ -515,7 +515,7 @@ def test_modify_target_group():
         HealthCheckPort="8081",
         HealthCheckPath="/status",
         HealthCheckIntervalSeconds=10,
-        HealthCheckTimeoutSeconds=10,
+        HealthCheckTimeoutSeconds=8,
         HealthyThresholdCount=10,
         UnhealthyThresholdCount=4,
         Matcher={"HttpCode": "200-399"},
@@ -527,7 +527,7 @@ def test_modify_target_group():
     assert response["TargetGroups"][0]["HealthCheckPath"] == "/status"
     assert response["TargetGroups"][0]["HealthCheckPort"] == "8081"
     assert response["TargetGroups"][0]["HealthCheckProtocol"] == "HTTPS"
-    assert response["TargetGroups"][0]["HealthCheckTimeoutSeconds"] == 10
+    assert response["TargetGroups"][0]["HealthCheckTimeoutSeconds"] == 8
     assert response["TargetGroups"][0]["HealthyThresholdCount"] == 10
     assert response["TargetGroups"][0]["Protocol"] == "HTTP"
     assert response["TargetGroups"][0]["ProtocolVersion"] == "HTTP1"
@@ -538,23 +538,35 @@ def test_modify_target_group():
 @mock_ec2
 @pytest.mark.parametrize("target_type", ["instance", "ip", "lambda", "alb", "other"])
 def test_create_target_group_with_target_type(target_type):
-    response, _, _, _, _, conn = create_load_balancer()
+    response, vpc, _, _, _, conn = create_load_balancer()
+
+    args = {
+        "Name": "a-target",
+        "TargetType": target_type,
+    }
+
+    if target_type != "lambda":
+        args["Protocol"] = "HTTP"
+        args["Port"] = 80
+        args["VpcId"] = vpc.id
 
-    response = conn.create_target_group(Name="a-target", TargetType=target_type)
+    response = conn.create_target_group(**args)
 
     group = response["TargetGroups"][0]
     assert "TargetGroupArn" in group
     assert group["TargetGroupName"] == "a-target"
     assert group["TargetType"] == target_type
-    assert "Protocol" not in group
-    assert "VpcId" not in group
+    if target_type != "lambda":
+        assert "Protocol" in group
+        assert "VpcId" in group
 
     group = conn.describe_target_groups()["TargetGroups"][0]
     assert "TargetGroupArn" in group
     assert group["TargetGroupName"] == "a-target"
     assert group["TargetType"] == target_type
-    assert "Protocol" not in group
-    assert "VpcId" not in group
+    if target_type != "lambda":
+        assert "Protocol" in group
+        assert "VpcId" in group
 
 
 @mock_elbv2
@@ -721,3 +733,72 @@ def test_delete_target_group_while_listener_still_exists():
 
     # Deletion does succeed now that the listener is deleted
     client.delete_target_group(TargetGroupArn=target_group_arn1)
+
+
+@mock_elbv2
+def test_create_target_group_validation_error():
+    elbv2 = boto3.client("elbv2", region_name="us-east-1")
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            Protocol="HTTP",
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert err["Message"] == "A port must be specified"
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            Protocol="HTTP",
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert err["Message"] == "A port must be specified"
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            Protocol="HTTP",
+            Port=8080,
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert err["Message"] == "A VPC ID must be specified"
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            Protocol="HTTP",
+            Port=8080,
+            VpcId="non-existing",
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert err["Message"] == "The VPC ID 'non-existing' is not found"
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            TargetType="lambda",
+            HealthCheckIntervalSeconds=5,
+            HealthCheckTimeoutSeconds=5,
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert (
+        err["Message"]
+        == "Health check timeout '5' must be smaller than the interval '5'"
+    )
+
+    with pytest.raises(ClientError) as ex:
+        elbv2.create_target_group(
+            Name="a-target",
+            TargetType="lambda",
+            HealthCheckIntervalSeconds=5,
+            HealthCheckTimeoutSeconds=6,
+        )
+    err = ex.value.response["Error"]
+    assert err["Code"] == "ValidationError"
+    assert err["Message"] == "Health check interval must be greater than the timeout."
diff --git a/tests/test_iam/test_iam_access_integration.py b/tests/test_iam/test_iam_access_integration.py
index 70a8c03fe..b977fb8aa 100644
--- a/tests/test_iam/test_iam_access_integration.py
+++ b/tests/test_iam/test_iam_access_integration.py
@@ -1,5 +1,9 @@
+import datetime
+
 import boto3
-from moto import mock_ec2, mock_iam
+from moto import mock_ec2, mock_iam, mock_sts, settings
+from moto.iam.models import iam_backends, IAMBackend
+from tests import DEFAULT_ACCOUNT_ID
 
 
 @mock_ec2
@@ -23,3 +27,36 @@ def test_invoking_ec2_mark_access_key_as_used():
     assert "LastUsedDate" in last_used
     assert last_used["ServiceName"] == "ec2"
     assert last_used["Region"] == "us-east-2"
+
+
+@mock_iam
+@mock_sts
+def test_mark_role_as_last_used():
+    role_name = "role_name_created_jan_1st"
+    iam = boto3.client("iam", "us-east-1")
+    sts = boto3.client("sts", "us-east-1")
+
+    role_arn = iam.create_role(RoleName=role_name, AssumeRolePolicyDocument="example")[
+        "Role"
+    ]["Arn"]
+
+    creds = sts.assume_role(RoleArn=role_arn, RoleSessionName="temp_session")[
+        "Credentials"
+    ]
+
+    iam2 = boto3.client(
+        "iam",
+        "us-east-1",
+        aws_access_key_id=creds["AccessKeyId"],
+        aws_secret_access_key=creds["SecretAccessKey"],
+        aws_session_token=creds["SessionToken"],
+    )
+
+    iam2.create_role(RoleName="name", AssumeRolePolicyDocument="example")
+
+    role = iam.get_role(RoleName=role_name)["Role"]
+    assert isinstance(role["RoleLastUsed"]["LastUsedDate"], datetime.datetime)
+
+    if not settings.TEST_SERVER_MODE:
+        iam: IAMBackend = iam_backends[DEFAULT_ACCOUNT_ID]["global"]
+        assert iam.get_role(role_name).last_used is not None
diff --git a/tests/test_iam/test_iam_resets.py b/tests/test_iam/test_iam_resets.py
index 0422a0ec7..49a2397c4 100644
--- a/tests/test_iam/test_iam_resets.py
+++ b/tests/test_iam/test_iam_resets.py
@@ -6,8 +6,8 @@ from moto import mock_iam
 
 # Test IAM User Inline Policy
 def test_policies_are_not_kept_after_mock_ends():
-    iam_client = boto3.client("iam", "us-east-1")
     with mock_iam():
+        iam_client = boto3.client("iam", "us-east-1")
         role_name = "test"
         assume_role_policy_document = {
             "Version": "2012-10-17",
diff --git a/tests/test_s3/test_s3_bucket_policy.py b/tests/test_s3/test_s3_bucket_policy.py
index 1b7bbea62..8a910c61c 100644
--- a/tests/test_s3/test_s3_bucket_policy.py
+++ b/tests/test_s3/test_s3_bucket_policy.py
@@ -34,15 +34,25 @@ class TestBucketPolicy:
     def teardown_class(cls):
         cls.server.stop()
 
+    xfail_reason = "S3 logic for resource-based policy is not yet correctly implemented, see https://github.com/getmoto/moto/pull/6799#issuecomment-1712799688"
+
     @pytest.mark.parametrize(
         "kwargs,status",
         [
             ({}, 200),
             ({"resource": "arn:aws:s3:::mybucket/test_txt"}, 200),
-            ({"resource": "arn:aws:s3:::notmybucket/*"}, 403),
-            ({"resource": "arn:aws:s3:::mybucket/other*"}, 403),
+            pytest.param(
+                {"resource": "arn:aws:s3:::notmybucket/*"},
+                403,
+                marks=pytest.mark.xfail(reason=xfail_reason),
+            ),
+            pytest.param(
+                {"resource": "arn:aws:s3:::mybucket/other*"},
+                403,
+                marks=pytest.mark.xfail(reason=xfail_reason),
+            ),
             ({"resource": ["arn:aws:s3:::mybucket", "arn:aws:s3:::mybucket/*"]}, 200),
-            (
+            pytest.param(
                 {
                     "resource": [
                         "arn:aws:s3:::notmybucket",
@@ -50,12 +60,16 @@ class TestBucketPolicy:
                     ]
                 },
                 403,
+                marks=pytest.mark.xfail(reason=xfail_reason),
             ),
-            (
+            pytest.param(
                 {"resource": ["arn:aws:s3:::mybucket", "arn:aws:s3:::notmybucket/*"]},
                 403,
+                marks=pytest.mark.xfail(reason=xfail_reason),
+            ),
+            pytest.param(
+                {"effect": "Deny"}, 403, marks=pytest.mark.xfail(reason=xfail_reason)
             ),
-            ({"effect": "Deny"}, 403),
         ],
     )
     def test_block_or_allow_get_object(self, kwargs, status):
diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py
index 3f7c43662..dc461b960 100644
--- a/tests/test_sqs/test_sqs.py
+++ b/tests/test_sqs/test_sqs.py
@@ -3176,8 +3176,6 @@ def test_message_delay_is_more_than_15_minutes():
 
     assert sorted([entry["Id"] for entry in response["Failed"]]) == ["id_2"]
 
-    # print(response)
-
     time.sleep(4)
 
     response = client.receive_message(