diff --git a/.gitignore b/.gitignore index 7aa60a8..a9dc95e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ pyvenv.cfg **/.pytest_cache **/.coverage **/coverage-reports/ +.coverage.* # linting, scanning configurations, sonarqube .scannerwork/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ebad7..7c4317d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.1] - 2023-04-18 + +### Changed + +- Enabled Amazon S3 server access logging on the logging bucket +- Upgraded CDK version to 2.75.0 + ## [1.4.0] - 2023-03-29 ### Changed diff --git a/README.md b/README.md index 154d485..b75a38e 100644 --- a/README.md +++ b/README.md @@ -609,7 +609,7 @@ The following procedures assumes that all the OS-level configuration has been co - [AWS Command Line Interface](https://aws.amazon.com/cli/) - [Python](https://www.python.org/) 3.9 or newer - [Node.js](https://nodejs.org/en/) 16.x or newer -- [AWS CDK](https://aws.amazon.com/cdk/) 2.44.0 or newer +- [AWS CDK](https://aws.amazon.com/cdk/) 2.75.0 or newer - [Amazon Corretto OpenJDK](https://docs.aws.amazon.com/corretto/) 17.0.4.1 > **Please ensure you test the templates before updating any production deployments.** diff --git a/source/cdk_solution_helper_py/README.md b/source/cdk_solution_helper_py/README.md index 4f6abcd..2bdd645 100644 --- a/source/cdk_solution_helper_py/README.md +++ b/source/cdk_solution_helper_py/README.md @@ -12,7 +12,7 @@ This README summarizes using the tool. Install this package. It requires at least - Python 3.9 -- AWS CDK version 2.44.0 or higher +- AWS CDK version 2.75.0 or higher To install the packages: diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py index 6956347..fe3555a 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/__init__.py @@ -15,7 +15,7 @@ from aws_solutions.cdk.context import SolutionContext from aws_solutions.cdk.stack import SolutionStack -from aws_solutions.cdk.synthesizers import SolutionStackSubstitions +from aws_solutions.cdk.synthesizers import SolutionStackSubstitutions class CDKSolution: @@ -31,11 +31,11 @@ class CDKSolution: def __init__(self, cdk_json_path: Path, qualifier="hnb659fds"): self.qualifier = qualifier self.context = SolutionContext(cdk_json_path=cdk_json_path) - self.synthesizer = SolutionStackSubstitions(qualifier=self.qualifier) + self.synthesizer = SolutionStackSubstitutions(qualifier=self.qualifier) def reset(self) -> None: """ Get a new synthesizer for this CDKSolution - useful for testing :return: None """ - self.synthesizer = SolutionStackSubstitions(qualifier=self.qualifier, generate_bootstrap_version_rule=False) + self.synthesizer = SolutionStackSubstitutions(qualifier=self.qualifier, generate_bootstrap_version_rule=False) diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt index 1bb43d3..49d08fa 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/aws_lambda/layers/aws_lambda_powertools/requirements/requirements.txt @@ -1,2 +1,2 @@ -aws-lambda-powertools==2.10.0 -aws-xray-sdk==2.11.0 \ No newline at end of file +aws-lambda-powertools==2.14.0 +aws-xray-sdk==2.12.0 \ No newline at end of file diff --git a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py index 019cec7..705a90a 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py +++ b/source/cdk_solution_helper_py/helpers_cdk/aws_solutions/cdk/synthesizers.py @@ -16,13 +16,13 @@ import re import shutil from contextlib import suppress -from dataclasses import field, dataclass +from dataclasses import dataclass, field from fileinput import FileInput from pathlib import Path -from typing import List, Dict, Tuple +from typing import Dict, List, Tuple import jsii -from aws_cdk import IStackSynthesizer, DefaultStackSynthesizer, ISynthesisSession +from aws_cdk import DefaultStackSynthesizer, IStackSynthesizer, ISynthesisSession logger = logging.getLogger("cdk-helper") @@ -256,7 +256,7 @@ def save(self, asset_path_global: Path = None, asset_path_regional: Path = None) @jsii.implements(IStackSynthesizer) -class SolutionStackSubstitions(DefaultStackSynthesizer): +class SolutionStackSubstitutions(DefaultStackSynthesizer): """Used to handle AWS Solutions template substitutions and sanitization""" substitutions = None @@ -295,11 +295,11 @@ def synthesize(self, session: ISynthesisSession): logger.info(f"solutions parameter substitution in {session.assembly.outdir} started") for template in self._template_names(session): - logger.info(f"substutiting parameters in {str(template)}") + logger.info(f"substituting parameters in {str(template)}") with FileInput(template, inplace=True) as template_lines: for line in template_lines: - # handle all template subsitutions in the line - for match in SolutionStackSubstitions.substitution_re.findall(line): + # handle all template substitutions in the line + for match in SolutionStackSubstitutions.substitution_re.findall(line): placeholder = match.replace("%", "") replacement = self._stack.node.try_get_context(placeholder) if not replacement: diff --git a/source/cdk_solution_helper_py/helpers_cdk/setup.py b/source/cdk_solution_helper_py/helpers_cdk/setup.py index 8211189..3bf6071 100644 --- a/source/cdk_solution_helper_py/helpers_cdk/setup.py +++ b/source/cdk_solution_helper_py/helpers_cdk/setup.py @@ -50,7 +50,7 @@ def get_version(): }, install_requires=[ "pip>=22.3.1", - "aws_cdk_lib==2.44.0", + "aws_cdk_lib==2.75.0", "Click==8.1.3", "boto3==1.26.47", "requests==2.28.1", diff --git a/source/cdk_solution_helper_py/requirements-dev.txt b/source/cdk_solution_helper_py/requirements-dev.txt index 38f633e..147bf71 100644 --- a/source/cdk_solution_helper_py/requirements-dev.txt +++ b/source/cdk_solution_helper_py/requirements-dev.txt @@ -1,5 +1,5 @@ -aws_cdk_lib==2.44.0 -aws-cdk.aws-servicecatalogappregistry-alpha==2.44.0a0 +aws_cdk_lib==2.75.0 +aws-cdk.aws-servicecatalogappregistry-alpha==2.75.0a0 black boto3==1.26.47 requests==2.28.1 diff --git a/source/infrastructure/cdk.json b/source/infrastructure/cdk.json index ce6dfbc..1db6d6d 100644 --- a/source/infrastructure/cdk.json +++ b/source/infrastructure/cdk.json @@ -3,8 +3,9 @@ "context": { "SOLUTION_NAME": "Maintaining Personalized Experiences with Machine Learning", "SOLUTION_ID": "SO0170", - "SOLUTION_VERSION": "v1.4.0", + "SOLUTION_VERSION": "v1.4.1", "APP_REGISTRY_NAME": "personalized-experiences-ML", - "APPLICATION_TYPE": "AWS-Solutions" + "APPLICATION_TYPE": "AWS-Solutions", + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true } } \ No newline at end of file diff --git a/source/infrastructure/personalize/s3/utils.py b/source/infrastructure/personalize/s3/utils.py index 40af078..d9ad398 100644 --- a/source/infrastructure/personalize/s3/utils.py +++ b/source/infrastructure/personalize/s3/utils.py @@ -15,12 +15,11 @@ from typing import List import aws_cdk.aws_iam as iam -from aws_cdk import RemovalPolicy, CfnResource -from aws_cdk.aws_s3 import Bucket, BucketEncryption, BlockPublicAccess +from aws_cdk import CfnResource, RemovalPolicy +from aws_cdk.aws_s3 import BlockPublicAccess, Bucket, BucketEncryption +from aws_solutions.cdk.cfn_nag import CfnNagSuppression, add_cfn_nag_suppressions from constructs import Construct -from aws_solutions.cdk.cfn_nag import add_cfn_nag_suppressions, CfnNagSuppression - logger = logging.getLogger("cdk-helper") diff --git a/source/infrastructure/personalize/stack.py b/source/infrastructure/personalize/stack.py index 4ab8e97..92ca482 100644 --- a/source/infrastructure/personalize/stack.py +++ b/source/infrastructure/personalize/stack.py @@ -12,51 +12,45 @@ # ###################################################################################################################### from aws_cdk import ( - CfnCondition, - Fn, + Aspects, Aws, - Duration, + CfnCondition, CfnOutput, CfnParameter, + Duration, + Fn, Tags, - Aspects, ) from aws_cdk.aws_events import EventBus from aws_cdk.aws_s3 import EventType, NotificationKeyFilter from aws_cdk.aws_s3_notifications import LambdaDestination -from aws_cdk.aws_stepfunctions import ( - StateMachine, - Chain, - Parallel, - TaskInput, -) -from constructs import Construct - +from aws_cdk.aws_stepfunctions import Chain, Parallel, StateMachine, TaskInput from aws_solutions.cdk.aws_lambda.cfn_custom_resources.resource_name import ResourceName from aws_solutions.cdk.aws_lambda.layers.aws_lambda_powertools import PowertoolsLayer from aws_solutions.cdk.cfn_nag import ( + CfnNagSuppressAll, CfnNagSuppression, add_cfn_nag_suppressions, - CfnNagSuppressAll, ) from aws_solutions.cdk.stack import SolutionStack from aws_solutions.scheduler.cdk.construct import Scheduler +from constructs import Construct from personalize.aws_lambda.functions import ( - S3EventHandler, - CreateDatasetGroup, - CreateSchema, + CreateBatchInferenceJob, + CreateBatchSegmentJob, + CreateCampaign, + CreateConfig, CreateDataset, + CreateDatasetGroup, CreateDatasetImportJob, CreateEventTracker, + CreateFilter, + CreateRecommender, + CreateSchema, CreateSolution, CreateSolutionVersion, - CreateCampaign, - CreateFilter, - CreateBatchInferenceJob, CreateTimestamp, - CreateConfig, - CreateBatchSegmentJob, - CreateRecommender, + S3EventHandler, ) from personalize.aws_lambda.functions.prepare_input import PrepareInput from personalize.aws_lambda.layers import SolutionsLayer @@ -79,6 +73,7 @@ class PersonalizeStack(SolutionStack): def __init__(self, scope: Construct, construct_id: str, *args, **kwargs) -> None: super().__init__(scope, construct_id, *args, **kwargs) + self.synthesizer.bind(self) # CloudFormation Parameters self.email = CfnParameter( diff --git a/source/infrastructure/setup.py b/source/infrastructure/setup.py index b5d7fbc..d606b00 100644 --- a/source/infrastructure/setup.py +++ b/source/infrastructure/setup.py @@ -35,7 +35,7 @@ author="AWS Solutions Builders", packages=setuptools.find_packages(), install_requires=[ - "aws-cdk-lib==2.44.0", + "aws-cdk-lib==2.75.0", "pip>=22.3.1", ], python_requires=">=3.9", diff --git a/source/requirements-dev.txt b/source/requirements-dev.txt index 4724eb8..c90a684 100644 --- a/source/requirements-dev.txt +++ b/source/requirements-dev.txt @@ -1,9 +1,9 @@ avro==1.11.1 black boto3==1.26.47 -aws_cdk_lib==2.44.0 -aws_solutions_constructs.aws_lambda_sns==2.25.0 -aws-cdk.aws-servicecatalogappregistry-alpha==2.44.0a0 +aws_cdk_lib==2.75.0 +aws_solutions_constructs.aws_lambda_sns==2.38.0 +aws-cdk.aws-servicecatalogappregistry-alpha==2.75.0a0 requests==2.28.1 crhelper==2.0.11 cronex==0.1.3.1 diff --git a/source/scheduler/README.md b/source/scheduler/README.md index 5b42176..f3929fb 100644 --- a/source/scheduler/README.md +++ b/source/scheduler/README.md @@ -11,7 +11,7 @@ This README summarizes using the scheduler. Install this package. It requires at least: - Python 3.9 -- AWS CDK version 2.44.0 or higher +- AWS CDK version 2.75.0 or higher To install the packages: diff --git a/source/scheduler/cdk/setup.py b/source/scheduler/cdk/setup.py index 908bce1..906eff5 100644 --- a/source/scheduler/cdk/setup.py +++ b/source/scheduler/cdk/setup.py @@ -43,7 +43,7 @@ def get_version(): packages=setuptools.find_namespace_packages(exclude=["build*"]), install_requires=[ "pip>=22.3.1", - "aws_cdk_lib==2.44.0", + "aws_cdk_lib==2.75.0", "Click==8.1.3", "boto3==1.26.47", ], diff --git a/source/scheduler/common/setup.py b/source/scheduler/common/setup.py index a0f6e7f..f9810b2 100644 --- a/source/scheduler/common/setup.py +++ b/source/scheduler/common/setup.py @@ -43,8 +43,8 @@ def get_version(): packages=setuptools.find_namespace_packages(exclude=["build*"]), install_requires=[ "pip>=22.3.1", - "aws-lambda-powertools==2.10.0", - "aws-xray-sdk==2.11.0", + "aws-lambda-powertools==2.14.0", + "aws-xray-sdk==2.12.0", "aws-solutions-python==2.0.0", "click==8.1.3", "cronex==0.1.3.1", diff --git a/source/tests/aspects/test_personalize_app_stack.py b/source/tests/aspects/test_personalize_app_stack.py index 92ab843..96604a1 100644 --- a/source/tests/aspects/test_personalize_app_stack.py +++ b/source/tests/aspects/test_personalize_app_stack.py @@ -13,16 +13,17 @@ # the specific language governing permissions and limitations under the License. # # ##################################################################################################################### -import pytest from pathlib import Path + import aws_cdk as cdk -from aws_cdk.assertions import Template, Capture +import pytest +from aws_cdk.assertions import Capture, Template from aws_solutions.cdk import CDKSolution solution = CDKSolution(cdk_json_path=Path(__file__).parent.parent.absolute() / "infrastructure" / "cdk.json") -from infrastructure.personalize.stack import PersonalizeStack from infrastructure.aspects.app_registry import AppRegistry +from infrastructure.personalize.stack import PersonalizeStack @pytest.fixture(scope="module") @@ -66,11 +67,11 @@ def test_service_catalog_registry_application(synth_template): "Tags": { "SOLUTION_ID": "SO0170", "SOLUTION_NAME": "Maintaining Personalized Experiences with Machine Learning", - "SOLUTION_VERSION": "v1.4.0", + "SOLUTION_VERSION": "v1.4.1", "Solutions:ApplicationType": "AWS-Solutions", "Solutions:SolutionID": "SO0170", "Solutions:SolutionName": "Maintaining Personalized Experiences with Machine Learning", - "Solutions:SolutionVersion": "v1.4.0", + "Solutions:SolutionVersion": "v1.4.1", }, }, ) diff --git a/source/tests/cdk_solution_helper/test_synthesizers.py b/source/tests/cdk_solution_helper/test_synthesizers.py index ea9d458..0d254da 100644 --- a/source/tests/cdk_solution_helper/test_synthesizers.py +++ b/source/tests/cdk_solution_helper/test_synthesizers.py @@ -15,12 +15,9 @@ import pytest from aws_cdk import App, Stack - from aws_solutions.cdk.interfaces import TemplateOptions from aws_solutions.cdk.mappings import Mappings -from aws_solutions.cdk.synthesizers import ( - SolutionStackSubstitions, -) +from aws_solutions.cdk.synthesizers import SolutionStackSubstitutions @pytest.fixture @@ -45,9 +42,10 @@ def template(): if ctx_var_val: context[ctx_var] = ctx_var_val - synth = SolutionStackSubstitions() + synth = SolutionStackSubstitutions() app = App(context=context) stack = Stack(app, "stack-id-1", synthesizer=synth) + stack.synthesizer.bind(stack) TemplateOptions(stack, "id_1", "description_1", "stack_1.template") Mappings(stack, "SO001") @@ -56,6 +54,7 @@ def template(): yield app.synth().stacks[0].template + def test_cloudformation_template_init(template): assert template["Parameters"] assert template["Rules"]["CheckBootstrapVersion"] diff --git a/source/tests/conftest.py b/source/tests/conftest.py index f6d4cde..9e49897 100644 --- a/source/tests/conftest.py +++ b/source/tests/conftest.py @@ -28,6 +28,7 @@ LayerVersionProps, Runtime, ) +from aws_solutions.cdk.synthesizers import SolutionStackSubstitutions from aws_solutions.core import get_service_client from botocore.stub import Stubber from constructs import Construct @@ -44,6 +45,7 @@ class Solution: id = "SO0170test" version = "99.99.99" + synthesizer = SolutionStackSubstitutions() @property def context(self): @@ -53,6 +55,7 @@ def context(self): "SOLUTION_VERSION": self.version, "APP_REGISTRY_NAME": "personalized-experiences-ML", "APPLICATION_TYPE": "AWS-Solutions", + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": True, } diff --git a/source/tests/test_deploy.py b/source/tests/test_deploy.py index 82e681c..a7bf24f 100644 --- a/source/tests/test_deploy.py +++ b/source/tests/test_deploy.py @@ -11,48 +11,38 @@ # the specific language governing permissions and limitations under the License. # # ###################################################################################################################### -import sys -from pathlib import Path - import pytest - -@pytest.fixture -def cdk_entrypoint(): - """This otherwise would not be importable (it's not in a package, and is intended to be a script)""" - sys.path.append(str((Path(__file__).parent.parent / "infrastructure").absolute())) - yield +extra_context = "EXTRA_CONTEXT" +source_bucket = "SOURCE_BUCKET" -def test_deploy(solution, cdk_entrypoint): - from deploy import build_app, solution as cdk_solution +@pytest.fixture +def build_stacks_for_buckets(): + """Ensure parameter ordering is kept""" + from deploy import build_app + from deploy import solution as cdk_solution cdk_solution.reset() - extra_context = "EXTRA_CONTEXT" - source_bucket = "SOURCE_BUCKET" synth = build_app({extra_context: extra_context, "BUCKET_NAME": source_bucket}) - stack = synth.get_stack_by_name("PersonalizeStack") - assert solution.id in stack.template["Description"] - assert source_bucket == stack.template["Mappings"]["SourceCode"]["General"]["S3Bucket"] - assert solution.id == stack.template["Mappings"]["Solution"]["Data"]["ID"] - assert "Yes" == stack.template["Mappings"]["Solution"]["Data"]["SendAnonymousUsageData"] - assert stack.template["Outputs"]["PersonalizeBucketName"] - assert stack.template["Outputs"]["SchedulerTableName"] - assert stack.template["Outputs"]["SNSTopicArn"] + stack = synth.get_stack_by_name("PersonalizeStack").template + yield stack -def test_parameters(solution, cdk_entrypoint): - """Ensure parameter ordering is kept""" - from deploy import build_app, solution as cdk_solution +def test_deploy(solution, build_stacks_for_buckets): + stack = build_stacks_for_buckets + assert solution.id in stack["Description"] + assert source_bucket == stack["Mappings"]["SourceCode"]["General"]["S3Bucket"] + assert solution.id == stack["Mappings"]["Solution"]["Data"]["ID"] + assert "Yes" == stack["Mappings"]["Solution"]["Data"]["SendAnonymousUsageData"] + assert stack["Outputs"]["PersonalizeBucketName"] + assert stack["Outputs"]["SchedulerTableName"] + assert stack["Outputs"]["SNSTopicArn"] - cdk_solution.reset() - - extra_context = "EXTRA_CONTEXT" - source_bucket = "SOURCE_BUCKET" - synth = build_app({extra_context: extra_context, "BUCKET_NAME": source_bucket}) - stack = synth.get_stack_by_name("PersonalizeStack").template +def test_parameters(build_stacks_for_buckets): + stack = build_stacks_for_buckets assert ( stack["Metadata"]["AWS::CloudFormation::Interface"]["ParameterGroups"][0]["Label"]["default"] == "Solution Configuration" @@ -63,3 +53,69 @@ def test_parameters(solution, cdk_entrypoint): stack["Metadata"]["AWS::CloudFormation::Interface"]["ParameterLabels"]["PersonalizeKmsKeyArn"]["default"] == "(Optional) KMS key ARN used to encrypt Datasets managed by Amazon Personalize" ) + + +def test_personalize_bucket(build_stacks_for_buckets): + stack = build_stacks_for_buckets + personalize_bucket = stack["Resources"]["PersonalizeBucket"] + + # Personalize bucket + assert personalize_bucket["Type"] == "AWS::S3::Bucket" + assert ( + personalize_bucket["Properties"]["LoggingConfiguration"]["DestinationBucketName"]["Ref"] == "AccessLogsBucket" + ) + assert ( + personalize_bucket["Properties"]["LoggingConfiguration"]["LogFilePrefix"] == "personalize-bucket-access-logs/" + ) + assert personalize_bucket["Properties"]["BucketEncryption"] == { + "ServerSideEncryptionConfiguration": [{"ServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}] + } + + assert personalize_bucket["Properties"]["PublicAccessBlockConfiguration"]["BlockPublicAcls"] == True + assert personalize_bucket["Properties"]["PublicAccessBlockConfiguration"]["BlockPublicPolicy"] == True + assert personalize_bucket["Properties"]["PublicAccessBlockConfiguration"]["IgnorePublicAcls"] == True + assert personalize_bucket["Properties"]["PublicAccessBlockConfiguration"]["RestrictPublicBuckets"] == True + + +def test_access_logs_bucket(build_stacks_for_buckets): + stack = build_stacks_for_buckets + access_logs_bucket = stack["Resources"]["AccessLogsBucket"] + assert access_logs_bucket["Type"] == "AWS::S3::Bucket" + assert access_logs_bucket["Properties"]["BucketEncryption"] == { + "ServerSideEncryptionConfiguration": [{"ServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}] + } + assert access_logs_bucket["Properties"]["PublicAccessBlockConfiguration"]["BlockPublicAcls"] == True + assert access_logs_bucket["Properties"]["PublicAccessBlockConfiguration"]["BlockPublicPolicy"] == True + assert access_logs_bucket["Properties"]["PublicAccessBlockConfiguration"]["IgnorePublicAcls"] == True + assert access_logs_bucket["Properties"]["PublicAccessBlockConfiguration"]["RestrictPublicBuckets"] == True + + bucket_policy = None + for resource, value in stack["Resources"].items(): + if "AccessLogsBucketPolicy" in resource: + bucket_policy = value + break + + assert bucket_policy["Type"] == "AWS::S3::BucketPolicy" + + access_logs_policy_statements = bucket_policy["Properties"]["PolicyDocument"]["Statement"] + assert len(access_logs_policy_statements) == 2 + + for policy in access_logs_policy_statements: + if "Sid" in policy and policy["Sid"] == "HttpsOnly": + assert policy["Principal"] == {"AWS": "*"} + assert policy["Action"] == "*" + assert policy["Condition"]["Bool"]["aws:SecureTransport"] == False + assert policy["Effect"] == "Deny" + assert policy["Resource"] == {"Fn::Join": ["", [{"Fn::GetAtt": ["AccessLogsBucket", "Arn"]}, "/*"]]} + + else: + assert policy["Principal"] == {"Service": "logging.s3.amazonaws.com"} + assert policy["Action"] == "s3:PutObject" + assert policy["Condition"] == { + "ArnLike": {"aws:SourceArn": {"Fn::GetAtt": ["PersonalizeBucket", "Arn"]}}, + "StringEquals": {"aws:SourceAccount": {"Ref": "AWS::AccountId"}}, + } + assert policy["Effect"] == "Allow" + assert policy["Resource"] == { + "Fn::Join": ["", [{"Fn::GetAtt": ["AccessLogsBucket", "Arn"]}, "/personalize-bucket-access-logs/*"]] + } diff --git a/source/tests/test_personalize_stack.py b/source/tests/test_personalize_stack.py index 760797f..48dc2c5 100644 --- a/source/tests/test_personalize_stack.py +++ b/source/tests/test_personalize_stack.py @@ -11,18 +11,34 @@ # the specific language governing permissions and limitations under the License. # # ###################################################################################################################### +import pytest from aws_cdk import App - +from aws_solutions.cdk.synthesizers import SolutionStackSubstitutions from infrastructure.personalize.stack import PersonalizeStack -def test_personalize_stack_email(solution): - app = App(context=solution.context) +@pytest.fixture +def emails_context(): + yield { + "SOLUTION_NAME": "Maintaining Personalized Experiences with Machine Learning", + "SOLUTION_ID": "99.99.99", + "SOLUTION_VERSION": "SO0170test", + "APP_REGISTRY_NAME": "personalized-experiences-ML", + "APPLICATION_TYPE": "AWS-Solutions", + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": True, + "BUCKET_NAME": "test-solution-bucket", + } + + +def test_personalize_stack_email(solution, emails_context, monkeypatch): + app = App(context=emails_context) + PersonalizeStack( app, "PersonalizeStack", description="meta-stack", - template_filename="maintaining-personalized-experiences-with-machine-learning.template", + template_filename="maintaining-personalized-experiences-with-machine-learning-test.template", + synthesizer=solution.synthesizer, ) synth = app.synth()