Repository: getmoto/moto (python)

Issue:
EKS DescribeNodegroup not returning id or name launch template attribute in response
The boto3 EKS DescribeNodegroup response returns the following for LaunchTemplate:
`'launchTemplate': {
            'name': 'string',
            'version': 'string',
            'id': 'string'
        },`

However, the moto response for same API call only returns either name or id - depending which you specify when creating a node group using boto3 EKS API. 

I've tested this out using boto3 API against endpoints on my own AWS account and when i do a describe_nodegroup API call it returns both launcTemplate.id and launchTemplate.name in the response. But moto only returns one or the other depending which one you specify in the create_nodegroup. I know you only specify one of id or name but when you call the describe_nodegroup - it should return both attributes in the response.
This is currently breaking for our specific test.

Pull request description:
EKS: NodeGroups: Verify LaunchTemplateID when providing name, and vice versa
Fixes #5879 

Patch:
diff --git a/docs/docs/services/s3.rst b/docs/docs/services/s3.rst
index 22152ed77..02522f6f7 100644
--- a/docs/docs/services/s3.rst
+++ b/docs/docs/services/s3.rst
@@ -119,6 +119,14 @@ s3
 
 - [X] put_bucket_ownership_controls
 - [X] put_bucket_policy
+  
+        Basic policy enforcement is in place.
+
+        Restrictions:
+         - Only statements with principal=* are taken into account
+         - Conditions are not taken into account
+        
+
 - [X] put_bucket_replication
 - [ ] put_bucket_request_payment
 - [X] put_bucket_tagging
diff --git a/moto/eks/models.py b/moto/eks/models.py
index 98681a893..30247c069 100644
--- a/moto/eks/models.py
+++ b/moto/eks/models.py
@@ -284,6 +284,25 @@ class ManagedNodegroup:
         self.tags = tags
         self.taints = taints
 
+        # Determine LaunchTemplateId from Name (and vice versa)
+        try:
+            from moto.ec2.models import ec2_backends
+
+            ec2 = ec2_backends[account_id][region_name]
+
+            template = None
+            if "name" in self.launch_template:
+                name = self.launch_template["name"]
+                template = ec2.describe_launch_templates(template_names=[name])[0]
+            elif "id" in self.launch_template:
+                _id = self.launch_template["id"]
+                template = ec2.describe_launch_templates(template_ids=[_id])[0]
+
+            self.launch_template["id"] = template.id
+            self.launch_template["name"] = template.name
+        except:  # noqa: E722 Do not use bare except
+            pass
+
     def __iter__(self):
         yield "nodegroupName", self.nodegroup_name
         yield "nodegroupArn", self.arn
diff --git a/moto/iam/access_control.py b/moto/iam/access_control.py
index a6a99d235..686657592 100644
--- a/moto/iam/access_control.py
+++ b/moto/iam/access_control.py
@@ -376,6 +376,8 @@ class IAMPolicyStatement(object):
                 is_action_concerned = True
 
         if is_action_concerned:
+            if self.is_unknown_principal(self._statement.get("Principal")):
+                return PermissionResult.NEUTRAL
             same_resource = self._match(self._statement["Resource"], resource)
             if self._statement["Effect"] == "Allow" and same_resource:
                 return PermissionResult.PERMITTED
@@ -384,6 +386,21 @@ class IAMPolicyStatement(object):
         else:
             return PermissionResult.NEUTRAL
 
+    def is_unknown_principal(self, principal) -> bool:
+        # https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-bucket-user-policy-specifying-principal-intro.html
+        # For now, Moto only verifies principal == *
+        # 'Unknown' principals are not verified
+        #
+        # This should be extended to check:
+        # - Can the principal be empty? How behaves AWS?
+        # - allow one/multiple account ARN's
+        # - allow one/multiple rules
+        if principal is None:
+            return False
+        if isinstance(principal, str) and principal != "*":
+            return True
+        return False
+
     def _check_element_matches(self, statement_element, value):
         if isinstance(self._statement[statement_element], list):
             for statement_element_value in self._statement[statement_element]:
diff --git a/moto/s3/models.py b/moto/s3/models.py
index f61d1fb37..6923f6c5e 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -12,6 +12,7 @@ import sys
 import urllib.parse
 
 from bisect import insort
+from typing import Optional
 from importlib import reload
 from moto.core import BaseBackend, BaseModel, BackendDict, CloudFormationModel
 from moto.core import CloudWatchMetricProvider
@@ -933,14 +934,14 @@ class FakeBucket(CloudFormationModel):
     def is_versioned(self):
         return self.versioning_status == "Enabled"
 
-    def allow_action(self, action, resource):
-        if self.policy is None:
-            return False
+    def get_permission(self, action, resource):
         from moto.iam.access_control import IAMPolicy, PermissionResult
 
+        if self.policy is None:
+            return PermissionResult.NEUTRAL
+
         iam_policy = IAMPolicy(self.policy.decode())
-        result = iam_policy.is_action_permitted(action, resource)
-        return result == PermissionResult.PERMITTED
+        return iam_policy.is_action_permitted(action, resource)
 
     def set_lifecycle(self, rules):
         self.rules = []
@@ -1679,6 +1680,13 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         return self.get_bucket(bucket_name).policy
 
     def put_bucket_policy(self, bucket_name, policy):
+        """
+        Basic policy enforcement is in place.
+
+        Restrictions:
+         - Only statements with principal=* are taken into account
+         - Conditions are not taken into account
+        """
         self.get_bucket(bucket_name).policy = policy
 
     def delete_bucket_policy(self, bucket_name):
@@ -1845,7 +1853,7 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         version_id=None,
         part_number=None,
         key_is_clean=False,
-    ):
+    ) -> Optional[FakeKey]:
         if not key_is_clean:
             key_name = clean_key_name(key_name)
         bucket = self.get_bucket(bucket_name)
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index bb281418c..205ba3c45 100644
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1150,29 +1150,48 @@ class S3Response(BaseResponse):
         key_name = self.parse_key_name(request, parsed_url.path)
         bucket_name = self.parse_bucket_name_from_url(request, full_url)
 
-        # Because we patch the requests library the boto/boto3 API
-        # requests go through this method but so do
-        # `requests.get("https://bucket-name.s3.amazonaws.com/file-name")`
-        # Here we deny public access to private files by checking the
-        # ACL and checking for the mere presence of an Authorization
-        # header.
-        if "Authorization" not in request.headers:
-            if hasattr(request, "url"):
-                signed_url = "Signature=" in request.url
-            elif hasattr(request, "requestline"):
-                signed_url = "Signature=" in request.path
+        # SDK requests tend to have Authorization set automatically
+        # If users make an HTTP-request, such as `requests.get("https://bucket-name.s3.amazonaws.com/file-name")`,
+        # The authorization-header may not be set
+        authorized_request = "Authorization" in request.headers
+        if hasattr(request, "url"):
+            signed_url = "Signature=" in request.url
+        elif hasattr(request, "requestline"):
+            signed_url = "Signature=" in request.path
+        try:
             key = self.backend.get_object(bucket_name, key_name)
-
-            if key and not signed_url:
-                bucket = self.backend.get_bucket(bucket_name)
-                resource = f"arn:aws:s3:::{bucket_name}/{key_name}"
-                bucket_policy_allows = bucket.allow_action("s3:GetObject", resource)
-                if not bucket_policy_allows and (key.acl and not key.acl.public_read):
+            bucket = self.backend.get_bucket(bucket_name)
+        except S3ClientError:
+            key = bucket = None
+        if key:
+            resource = f"arn:aws:s3:::{bucket_name}/{key_name}"
+
+            # Authorization Workflow
+            # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-auth-workflow-object-operation.html
+
+            # A bucket can deny all actions, regardless of who makes the request
+            from moto.iam.access_control import PermissionResult
+
+            action = f"s3:{method.upper()[0]}{method.lower()[1:]}Object"
+            bucket_permissions = bucket.get_permission(action, resource)
+            if bucket_permissions == PermissionResult.DENIED:
+                return 403, {}, ""
+
+            # If the request is not authorized, and not signed,
+            # that means that the action should be allowed for anonymous users
+            if not authorized_request and not signed_url:
+                # We already know that the bucket permissions do not explicitly deny this
+                # So bucket permissions are either not set, or do not explicitly allow
+                # Next check is to see if the ACL of the individual key allows this action
+                if bucket_permissions != PermissionResult.PERMITTED and (
+                    key.acl and not key.acl.public_read
+                ):
                     return 403, {}, ""
-            elif signed_url and not key:
-                # coming in from requests.get(s3.generate_presigned_url())
-                if self._invalid_headers(request.url, dict(request.headers)):
-                    return 403, {}, S3_INVALID_PRESIGNED_PARAMETERS
+
+        elif signed_url and not authorized_request:
+            # coming in from requests.get(s3.generate_presigned_url())
+            if self._invalid_headers(request.url, dict(request.headers)):
+                return 403, {}, S3_INVALID_PRESIGNED_PARAMETERS
 
         if hasattr(request, "body"):
             # Boto
diff --git a/setup.cfg b/setup.cfg
index b07a359dc..8f54e4ae5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -126,7 +126,7 @@ ec2instanceconnect =
 ecr =
 ecs =
 efs = sshpubkeys>=3.1.0
-eks =
+eks = sshpubkeys>=3.1.0
 elasticache =
 elasticbeanstalk =
 elastictranscoder =


Test patch:
diff --git a/tests/test_eks/test_eks_ec2.py b/tests/test_eks/test_eks_ec2.py
new file mode 100644
index 000000000..5e98203fd
--- /dev/null
+++ b/tests/test_eks/test_eks_ec2.py
@@ -0,0 +1,73 @@
+import boto3
+
+from moto import mock_ec2, mock_eks
+from .test_eks_constants import NODEROLE_ARN_VALUE, SUBNET_IDS
+
+
+@mock_eks
+def test_passing_an_unknown_launchtemplate_is_supported():
+    eks = boto3.client("eks", "us-east-2")
+    eks.create_cluster(name="a", roleArn=NODEROLE_ARN_VALUE, resourcesVpcConfig={})
+    group = eks.create_nodegroup(
+        clusterName="a",
+        nodegroupName="b",
+        launchTemplate={"name": "random"},
+        nodeRole=NODEROLE_ARN_VALUE,
+        subnets=SUBNET_IDS,
+    )["nodegroup"]
+
+    group["launchTemplate"].should.equal({"name": "random"})
+
+
+@mock_ec2
+@mock_eks
+def test_passing_a_known_launchtemplate_by_name():
+    ec2 = boto3.client("ec2", region_name="us-east-2")
+    eks = boto3.client("eks", "us-east-2")
+
+    lt_id = ec2.create_launch_template(
+        LaunchTemplateName="ltn",
+        LaunchTemplateData={
+            "TagSpecifications": [
+                {"ResourceType": "instance", "Tags": [{"Key": "t", "Value": "v"}]}
+            ]
+        },
+    )["LaunchTemplate"]["LaunchTemplateId"]
+
+    eks.create_cluster(name="a", roleArn=NODEROLE_ARN_VALUE, resourcesVpcConfig={})
+    group = eks.create_nodegroup(
+        clusterName="a",
+        nodegroupName="b",
+        launchTemplate={"name": "ltn"},
+        nodeRole=NODEROLE_ARN_VALUE,
+        subnets=SUBNET_IDS,
+    )["nodegroup"]
+
+    group["launchTemplate"].should.equal({"name": "ltn", "id": lt_id})
+
+
+@mock_ec2
+@mock_eks
+def test_passing_a_known_launchtemplate_by_id():
+    ec2 = boto3.client("ec2", region_name="us-east-2")
+    eks = boto3.client("eks", "us-east-2")
+
+    lt_id = ec2.create_launch_template(
+        LaunchTemplateName="ltn",
+        LaunchTemplateData={
+            "TagSpecifications": [
+                {"ResourceType": "instance", "Tags": [{"Key": "t", "Value": "v"}]}
+            ]
+        },
+    )["LaunchTemplate"]["LaunchTemplateId"]
+
+    eks.create_cluster(name="a", roleArn=NODEROLE_ARN_VALUE, resourcesVpcConfig={})
+    group = eks.create_nodegroup(
+        clusterName="a",
+        nodegroupName="b",
+        launchTemplate={"id": lt_id},
+        nodeRole=NODEROLE_ARN_VALUE,
+        subnets=SUBNET_IDS,
+    )["nodegroup"]
+
+    group["launchTemplate"].should.equal({"name": "ltn", "id": lt_id})
diff --git a/tests/test_s3/test_s3_bucket_policy.py b/tests/test_s3/test_s3_bucket_policy.py
index f96ba8b54..1bec7409a 100644
--- a/tests/test_s3/test_s3_bucket_policy.py
+++ b/tests/test_s3/test_s3_bucket_policy.py
@@ -4,6 +4,7 @@ import requests
 import pytest
 import sure  # noqa # pylint: disable=unused-import
 
+from botocore.exceptions import ClientError
 from moto.moto_server.threaded_moto_server import ThreadedMotoServer
 
 
@@ -36,24 +37,78 @@ class TestBucketPolicy:
             ({"resource": "arn:aws:s3:::mybucket/test_txt"}, 200),
             ({"resource": "arn:aws:s3:::notmybucket/*"}, 403),
             ({"resource": "arn:aws:s3:::mybucket/other*"}, 403),
-            ({"actions": ["s3:PutObject"]}, 403),
             ({"effect": "Deny"}, 403),
         ],
     )
-    def test_policy_allow_all(self, kwargs, status):
+    def test_block_or_allow_get_object(self, kwargs, status):
         self._put_policy(**kwargs)
 
+        if status == 200:
+            self.client.get_object(Bucket="mybucket", Key="test_txt")
+        else:
+            with pytest.raises(ClientError):
+                self.client.get_object(Bucket="mybucket", Key="test_txt")
+
         requests.get(self.key_name).status_code.should.equal(status)
 
+    def test_block_put_object(self):
+        # Block Put-access
+        self._put_policy(**{"effect": "Deny", "actions": ["s3:PutObject"]})
+
+        # GET still works
+        self.client.get_object(Bucket="mybucket", Key="test_txt")
+
+        # But Put (via boto3 or requests) is not allowed
+        with pytest.raises(ClientError) as exc:
+            self.client.put_object(Bucket="mybucket", Key="test_txt", Body="new data")
+        err = exc.value.response["Error"]
+        err["Message"].should.equal("Forbidden")
+
+        requests.put(self.key_name).status_code.should.equal(403)
+
+    def test_block_all_actions(self):
+        # Block all access
+        self._put_policy(**{"effect": "Deny", "actions": ["s3:*"]})
+
+        # Nothing works
+        with pytest.raises(ClientError) as exc:
+            self.client.get_object(Bucket="mybucket", Key="test_txt")
+        err = exc.value.response["Error"]
+        err["Message"].should.equal("Forbidden")
+
+        # But Put (via boto3 or requests) is not allowed
+        with pytest.raises(ClientError) as exc:
+            self.client.put_object(Bucket="mybucket", Key="test_txt", Body="new data")
+        err = exc.value.response["Error"]
+        err["Message"].should.equal("Forbidden")
+
+        requests.get(self.key_name).status_code.should.equal(403)
+        requests.put(self.key_name).status_code.should.equal(403)
+
+        # Allow access again, because we want to delete the object during teardown
+        self._put_policy(**{"effect": "Allow", "actions": ["s3:*"]})
+
+    def test_block_all_with_different_principal(self):
+        # Block all access for principal y
+        self._put_policy(**{"effect": "Deny", "actions": ["s3:*"], "principal": "y"})
+
+        # Everything works - Moto only blocks access for principal *
+        self.client.get_object(Bucket="mybucket", Key="test_txt")
+        self.client.put_object(Bucket="mybucket", Key="test_txt", Body="new data")
+
     def _put_policy(
-        self, resource="arn:aws:s3:::mybucket/*", effect="Allow", actions=None
+        self,
+        resource="arn:aws:s3:::mybucket/*",
+        effect="Allow",
+        actions=None,
+        principal=None,
     ):
         policy = {
             "Version": "2012-10-17",
             "Statement": [
                 {
                     "Effect": effect,
-                    "Principal": "*",
+                    "Principal": principal or "*",
                     "Action": actions or ["s3:GetObject"],
                     "Resource": resource,
                 }