def test_aws_config_bucket(stack: Stack) -> None: """Test AWS Config Bucket creation. Note that a bucket policy is also created when a Bucket is instanciated """ stack.add(AWSConfigBucket(name="test-bucket")) assert stack.export()["Resources"] == EXPECTED_AWS_CONFIG_BUCKET
def test_s3_access_managed_policy(stack: Stack) -> None: """Test S3 access managed policy creation.""" stack.add( S3AccessManagedPolicy( name="S3ManagedPolicy", buckets=["test-bucket"], action=["s3:PutObject"], roles=["TestRole"], ) ) assert stack.export()["Resources"] == EXPECTED_S3_ACCESS_MANAGED_POLICY
def test_config_rules(stack: Stack) -> None: """Test config rules creation.""" for config_rule in ( S3BucketPublicWriteProhibited, S3BucketPublicReadProhibited, S3BucketServerSideEncryptionEnabled, S3BucketSSLRequestsOnly, IAMUserNoPoliciesCheck, ): stack.add(config_rule) assert stack.export()["Resources"] == EXPECTED_RULES
def test_role(stack: Stack) -> None: """Test IAM role creation. Creating a Role also tests PolicyDocument and Policystatement classes. """ stack.add( Role( name="TestRole", description="TestRole description", principal={"Service": "test"}, ) ) assert stack.export()["Resources"] == EXPECTED_ROLE
def stack() -> Stack: """Stack fixture to help dumping dictionnaries from constructs.""" return Stack("test-stack", "this is a test stack")
def build_and_deploy_tstacks() -> None: """Build and deploy two simple troposphere stacks. Two stacks in two different regions are deployed. An us stack define only a secure bucket. An eu stack define secure s3 buckets, a role to add object to the eu bucket and a AWSConfig recorder with rules that check s3 buckets security configurations across both regions. """ sessions = { "eu": Session(regions=["eu-west-1"]), "us": Session(regions=["us-east-1"]), } stack = {} for region in ("eu", "us"): stack[region] = Stack( f"e3-example-{region}", sessions[region], opts={"Capabilities": ["CAPABILITY_NAMED_IAM"]}, ) # Add a s3 secure bucket in each region stack["eu"].add_construct([Bucket(name="e3-l1-example")]) stack["us"].add_construct([Bucket(name="e3-l2-example")]) # Define a new IAM-Roles that will be used to acces e3-l1-example bucket stack["eu"].add_construct( [ Role( name="L1WriteRole", description="Role to write to l1 buckets", principal={"Service": "ecs-tasks.amazonaws.com"}, ) ] ) # Define a new IAM-Policy to putObject in e3-l1-example bucket # and attach the L1WriteRole role to it stack["eu"].add_construct( [ S3AccessManagedPolicy( name="S3WriteAccess", buckets=["e3-l1-example"], action=["s3:PutObject"], roles=[Ref(stack["eu"]["L1WriteRole"])], ) ] ) # Add AWS config rules to check S3 buckets security configuration. # This should only be defined in one region for region in ("eu",): stack[region].add_construct( [ConfigurationRecorder(bucket_name="config-bucket-example")] ) for region in ("eu",): stack[region].add_construct( [ S3BucketPublicWriteProhibited, S3BucketPublicReadProhibited, S3BucketServerSideEncryptionEnabled, S3BucketSSLRequestsOnly, IAMUserNoPoliciesCheck, ] ) # Deploy stacks for region in ("eu", "us"): stack[region].deploy()
def test_instanciate() -> None: """Test stack instanciation.""" stack = Stack("test-stack", "this is a test stack") assert stack
def test_add_and_get_item() -> None: """Test adding a construct and retrieving an AWSObject from a stack.""" stack = Stack("test-stack", "this is a test stack") stack.add(Bucket("my-bucket")) my_bucket = stack["my-bucket"] assert my_bucket
def test_config_recorder(stack: Stack) -> None: """Test config recorder creation.""" stack.add(ConfigurationRecorder(bucket_name="config-test-bucket")) assert stack.export()["Resources"] == EXPECTED_RECORDER
def execute_for_stack(self, stack: Stack) -> int: """Execute application for a given stack and return exit status. :param Stack: the stack on which the application executes """ try: if self.args.command in ("push", "update"): if self.data_dir is not None and self.s3_data_key is not None: s3 = self.aws_env.client("s3") # synchronize data to the bucket before creating the stack for f in find(self.data_dir): with open(f, "rb") as fd: subkey = os.path.relpath(f, self.data_dir).replace( "\\", "/" ) logging.info( "Upload %s to %s:%s%s", subkey, self.s3_bucket, self.s3_data_key, subkey, ) s3.put_object( Bucket=self.s3_bucket, Body=fd, ServerSideEncryption="AES256", Key=self.s3_data_key + subkey, ) if self.s3_template_key is not None: logging.info( "Upload template to %s:%s", self.s3_bucket, self.s3_template_key ) s3.put_object( Bucket=self.s3_bucket, Body=stack.body.encode("utf-8"), ServerSideEncryption="AES256", Key=self.s3_template_key, ) logging.info("Validate template for stack %s" % stack.name) try: stack.validate(url=self.s3_template_url) except Exception: logging.error("Invalid cloud formation template") logging.error(stack.body) raise if stack.exists(): changeset_name = "changeset%s" % int(time.time()) logging.info("Push changeset: %s" % changeset_name) stack.create_change_set(changeset_name, url=self.s3_template_url) result = stack.describe_change_set(changeset_name) while result["Status"] in ("CREATE_PENDING", "CREATE_IN_PROGRESS"): time.sleep(1.0) result = stack.describe_change_set(changeset_name) if result["Status"] == "FAILED": logging.error(result["StatusReason"]) stack.delete_change_set(changeset_name) return 1 else: for el in result["Changes"]: if "ResourceChange" not in el: continue logging.info( "%-8s %-32s: (replacement:%s)", el["ResourceChange"].get("Action"), el["ResourceChange"].get("LogicalResourceId"), el["ResourceChange"].get("Replacement", "n/a"), ) if self.args.apply_changeset: ask = input("Apply change (y/N): ") if ask[0] in "Yy": return stack.execute_change_set( changeset_name=changeset_name, wait=True ) return 0 else: logging.info("Create new stack") stack.create(url=self.s3_template_url) state = stack.state() if self.args.wait_stack_creation: logging.info("waiting for stack creation...") while "PROGRESS" in state["Stacks"][0]["StackStatus"]: result = stack.resource_status(in_progress_only=False) time.sleep(10.0) state = stack.state() logging.info("done") elif self.args.command == "show": print(stack.body) elif self.args.command == "protect": # Enable termination protection result = stack.enable_termination_protection() if self.stack_policy_body is not None: stack.set_stack_policy(self.stack_policy_body) else: print("No stack policy to set") return 0 except botocore.exceptions.ClientError as e: logging.error(str(e)) return 1